semantic-dom-mcp
Drives a real Chromium browser to extract a page's DOM into compact Semantic JSON with Playwright-native locators, enabling AI agents to generate consistent, accurate Playwright tests.
README
semantic-dom-mcp
Local MCP server (stdio, Node.js + TypeScript) that drives a real Chromium browser via Playwright to extract a live page's DOM into compact, factual Semantic JSON with Playwright-native locators — so AI-generated Playwright tests are consistent across the whole QA team, not just accurate.
Same page → same extraction → same conventions → same test style, regardless of who runs it.
Evidence: benchmark/RESULTS.md — on real pages the Semantic JSON is 92–97% smaller than the raw DOM an agent would otherwise consume, with every locator uniqueness-verified by Playwright's engine and byte-identical output across runs. Docs: Team guide (setup + connecting your agent) · Benchmark methodology · Roadmap
Quickstart (5 lines)
npm install
npx playwright install chromium
npm run build
export QA_MCP_ALLOWED_HOSTS="staging.yourapp.internal" # PowerShell: $env:QA_MCP_ALLOWED_HOSTS="..."
node ./dist/index.js # or add the server to your MCP client (below) and let it launch this
Claude Code / client connection
{
"mcpServers": {
"semantic-dom": {
"command": "node",
"args": ["./semantic-dom-mcp/dist/index.js"],
"env": {
"QA_MCP_ALLOWED_HOSTS": "staging.yourapp.internal,staging.admin.internal",
"QA_MCP_STORAGE_STATE": "./.auth/staging.json"
}
}
}
}
During development, "command": "npx", "args": ["tsx", "./semantic-dom-mcp/src/index.ts"] may
replace the built path.
Workflow
- Ask your agent: "extract the checkout page and write a success-path test."
- The agent calls
extract_semantic_dom({ url })— the server navigates a real Chromium page, runs the extractor inside the page, and returns Semantic JSON: every interactive node with a ready-to-paste Playwright locator, uniqueness verified by Playwright's own engine. - The agent uses the
write_playwright_testprompt (scenario + the JSON), which injects the team conventions. - The result is a Playwright test in team style, grounded in real locators — never guessed ones.
MCP surface
| Kind | Name | Purpose |
|---|---|---|
| Tool | extract_semantic_dom |
Extract a URL into Semantic JSON (url, wait_for, wait_selector, include_hidden, max_nodes). Read-only, never touches the page. |
| Tool | extract_semantic_dom_after |
Same, but first runs a short declared action list (fill/click/press/wait, max 20) in the main frame and snapshots the resulting state — for toasts, validation errors, opened dialogs. Refuses to extract if the actions navigated off the allowlist. |
| Tool | check_auth |
Diagnostic: navigates with the configured storageState and reports whether the session bounced to a login-looking page (expired auth shows up as an answer, not a mystery). |
| Tool | list_frames |
Diagnostic frame tree with same-origin/reachability classification. |
| Prompt | write_playwright_test |
Team-standard test-writing prompt (scenario, extract_json, team_name?, framework_note?). |
| Resource | conventions://playwright |
The same team conventions as read-only text. |
Errors (navigation failure, denied host, missing selector) come back as structured JSON in the tool result — the agent can react instead of crashing.
Configuration (environment variables)
| Variable | Meaning |
|---|---|
QA_MCP_ALLOWED_HOSTS |
Required. Comma-separated hostnames the server may navigate to. Navigation is denied by default. Supports host, host:port, and *.domain entries. |
QA_MCP_STORAGE_STATE |
Optional path to a Playwright storageState JSON for pre-authenticated staging sessions. This file holds a live session — it is gitignored; never commit it. |
QA_MCP_TEAM_NAME |
Optional team name used in the write_playwright_test prompt (default QA). |
Security posture
- Tool inputs are untrusted (they arrive via an LLM): strict schemas (
additionalProperties: false), http/https only, host allowlist enforced before any navigation. extract_semantic_domonly reads the DOM — it never clicks, submits, or mutates the page. The one sanctioned exception isextract_semantic_dom_after, which executes only an explicit, bounded, schema-validated action list, never logs fill values, and aborts without extracting if the page leaves the allowlisted hosts.- No network egress beyond navigating to the target URL. No telemetry. Page contents are never logged (stderr carries only high-level events) and are not stored beyond the current call.
Semantics worth knowing
- Snapshot honesty: the JSON is a single moment. A disabled submit button is reported
is_disabled: truewith a note — the conventions instruct the model to write the interactions that change state, not to assume it stays disabled. - Hidden nodes are included and flagged
is_visible: false(tests often assert hidden-ness); passinclude_hidden: falseto drop them (the count dropped is noted, never silent). - Open shadow DOM is traversed and flagged
in_shadow— locators pierce it natively, so no>>>/::shadowCSS is ever emitted. Closed shadow roots appear asshadow_boundarymarker nodes (detected via pre-navigationattachShadowinstrumentation; closed roots created by declarative shadow DOM parse before scripts run and cannot be detected). - Same-origin iframes are extracted per-frame with
frame_pathset (chainframeLocator()in that order). Cross-origin iframes are recorded as opaquecross_origin_framenodes with URL/name only — their DOM is never touched. - Notification & dialog surfaces (
role="alert",role="status", dialogs) are extracted like interactive nodes. When a toast library keeps the live region empty and renders the message in a sibling (a common pattern across UI libraries), the message text is pulled from the enclosing container and flagged. For UI that renders late after an interaction,wait_selector_afteronextract_semantic_dom_afterwaits deterministically instead of guessingsettle_ms. Since those ARIA roles take names from the author (not contents), their role locator isgetByRole('alert')— or with thearia-labelname when one exists. For UI that only appears after an interaction (login-success toast), useextract_semantic_dom_after. - Links carry
href(schema 1.1) so agents can discover which page to extract next without scraping. Framework-generated ids (rc_select_*, ReactuseId, Radix, MUI...) are detected and demoted to last-resort with a note — they change between builds and must never be primary. viewport: "mobile"(375×812, touch) snapshots responsive states; visibility flags reflect the active media queries.- Truncation is loud:
max_nodes/ depth caps settruncated: trueplus a note. Non-unique locators carryis_unique: falseanddisambiguationguidance.
Development
npm run dev # run the server over stdio via tsx
npm run typecheck # tsc --noEmit (strict)
npm test # Vitest suites against real fixture pages in headless Chromium
Repo layout: src/index.ts (bootstrap) · src/server.ts (MCP surface) · src/browser.ts
(Playwright layer + orchestration) · src/extractor/ (in-page engine + locator resolution) ·
src/types.ts (frozen v1 contract) · src/conventions.ts (single source of team conventions).
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。