Semantic Hints MCP
Semantic Hints MCP is a lightweight server that reads compact UI semantic hints from a browser and exposes them as structured data, acting as a companion to the Playwright MCP for efficient agent workflows.
semantic_snapshot: Returns a compact, categorized map of UI elements annotated withdata-agent-id, grouped by:regions,actions,inputs,observables,navigation, andother. Options include:Navigate to a URL before extracting
Scope extraction to a subtree via a
data-agent-idor CSS selectorInclude hidden elements (excluded by default)
Returns element metadata (id, role, name, state, value, etc.) — never raw HTML, class names, or DOM subtrees
semantic_observe: Reads the live value/state of a single hinted element by itsdata-agent-id. Returns an error if no element or multiple elements match.
Typical workflow: Use semantic_snapshot to identify element IDs → pass those IDs to Playwright MCP (e.g., [data-agent-id='checkout.submit']) to perform actions → use semantic_observe to verify resulting state.
Integration: Shares a Chromium browser session with Playwright MCP via CDP (recommended), so both tools operate on the same live page. Configurable via environment variables (SEMANTIC_HINTS_CDP_URL, etc.), with a fallback to launching a private browser if no shared session is available.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Semantic Hints MCPTake a semantic snapshot of the current page"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Semantic Hints MCP
A small local stdio MCP server that exposes compact semantic observations from
a UI annotated with data-semtag-* hints.
It is a companion to the official Playwright MCP, not a replacement. It only reads semantics; all browser actions (click, type, navigate) stay in Playwright MCP. The intended split:
semantic_snapshot→ a compact map of hinted elements (~2–3× smaller than a Playwright ARIA snapshot of the same page).Pick a semantic
id, e.g.checkout.submit.Act via Playwright MCP using the selector
[data-semtag-id='checkout.submit'].semantic_observe→ the current value/state of one hinted element.
Browser / session architecture
The semantic-hints MCP uses the Playwright library directly. It does not call the Playwright MCP internally.
However, the semantic-hints MCP and the official Playwright MCP must observe/control the same browser state during an experiment. Avoid accidentally creating two unrelated browser sessions where:
Playwright MCP clicks in browser A
semantic-hints MCP observes browser B
That would invalidate the workflow.
Semantic-hints MCP must not silently launch a separate browser by default.
Preferred design: one shared Chromium over CDP
Start Chromium with remote debugging enabled, e.g.:
chromium --remote-debugging-port=9222(or the equivalent using your locally installed Chrome/Chromium binary).
Configure the official Playwright MCP to connect to that shared browser, if supported by the installed version/config.
The semantic-hints MCP connects to the same browser via Playwright:
chromium.connectOverCDP("http://127.0.0.1:9222")It reuses the active page where possible. If no suitable page exists and a URL is provided, it may open/navigate a page. If no page exists and no URL is provided, it returns a clear error asking for a URL or active page.
Configuration
Variable | Default | Purpose |
|
| CDP endpoint of the shared Chromium to attach to. |
| (unset) | Optional default app URL, opened when a tool is called with no |
|
| If |
|
| Headless mode for the standalone-launch fallback only. |
Behavior:
If
SEMANTIC_HINTS_CDP_URLis reachable, connect over CDP (the normal mode).If the CDP connection fails and
SEMANTIC_HINTS_LAUNCH_BROWSERisfalse, fail clearly with setup instructions — never silently launch.If
SEMANTIC_HINTS_LAUNCH_BROWSERistrue, the server may launch its own browser, but every tool response then carries awarningfield making clear this is a standalone session that may not share state with the Playwright MCP.
The normal research workflow should use the shared CDP browser mode.
Related MCP server: brocogni
Tools
semantic_snapshot
Returns hinted elements grouped by their data-semtag-role — navigation,
action, option, input, select, toggle, slider, observable,
region, collection — plus other, collections, url and screen.
Empty groups are omitted, and there is no counts block: the arrays state
their own lengths, so repeating them would only spend tokens.
{
"url": "http://localhost:8082/products", // optional: navigate first; else read current page
"scope": "products.filters", // optional: data-semtag-id or CSS selector subtree
"includeHidden": false // optional: include hidden elements (default false)
}Per element (fields included only when present): id, name, action,
state, target, controls, options (selects only), value (roles that
hold one), enabled, visible. Never returns HTML, class names, or DOM
subtrees.
There is no role field — the group key is the role, stated once for the whole
group instead of once per entry. The exception is other, where the key says
nothing: entries there keep the raw role string so an off-vocabulary value is
visible rather than silently erased. semantic_observe always includes role,
having no group around it.
The ten roles and the seven data-semtag-* attributes are defined in
semtag-injector-skill/semtag-inject/references/hint-design.md, the source of
truth for this server, the
hint-injection prompt, and the browser-agent prompt alike. Nothing is inferred
from tags or ARIA, and nothing is repaired: an element with a
data-semtag-action but no data-semtag-role is other, not an action. other
is how a hinting gap surfaces, so it is a thing to go fix in the app.
enabled and visible are omitted unless false — an element carrying neither
is enabled and visible. Since snapshots exclude hidden elements by default, a
"visible": true on every entry would only restate the filter.
Folded collections
A repeated collection is most of a real snapshot, and nearly all of it is repetition: every card restates its role and target, every price cell restates its role and state. So a collection is folded into one columnar block — shared attributes stated once, each item reduced to a row of only what varies:
"collections": [{
"id": "products.grid",
"name": "Products",
"idPattern": "products.grid.item.{key}[.{control}]",
"item": { "role": "navigation" },
"itemControls": { "price": { "role": "observable", "state": "product.price" },
"remove": { "role": "action", "name": "Remove",
"action": "remove-product" } },
"fields": ["key", "name", "target", "price.value", "remove"],
"items": [
["alpha", "Alpha Phone", "product.detail", "£10.00", true],
["beta", "Beta Laptop", "product.detail", "£20.00", true],
["gamma", "Gamma Bundle", "bundle.detail", "£30.00", null]
]
}]item/itemControls[c]hold the fields every item agrees on. (itemControls, notcontrols, because the container may carry its owndata-semtag-controls.)fieldsis the column header:"key"first, then the item's own varying fields by bare name, then"<control>.<field>".itemsrows align to it, andnullmarks an absent cell.An element only some items have gets a bare
"<control>"presence column holdingtrue/null— above,gammahas no Remove button. It is needed because a uniform optional control otherwise hoists away completely, leaving no cell that could be null. The item itself can be optional too, and then takes a column named"item".nameis dropped from a cell when it equalsvalue: for a text observable the accessible name is the displayed value.The bucket an element would have occupied is not carried. On real pages it only ever restated
role("navigation"/"navigation"), androlewithaction/target/statealready says everything an agent acts on.
Folding costs no addressability. idPattern states how to rebuild any
member's real data-semtag-id — products.grid.item.beta.price — which
semantic_observe and [data-semtag-id="…"] still resolve exactly as before.
Folded members and their container leave the six flat buckets, so nothing is stated twice.
Folding needs two things: at least two items using the <prefix>.item.<key> id
convention, and a DOM ancestor declaring data-semtag-role="collection".
Ancestry, not id equality — a container is often named a little differently from
the prefix its rows use (admin.products.list holding
admin.products.item.42), and that is a naming nicety, not a reason to give up
the compression. Hence id (the container's own hint) and idPattern (built
from the rows' prefix) can differ. A repeated group with no collection ancestor
at all is a hinting gap in the app, not something the extractor guesses at: it
stays flat, and the hint-review overlay flags it.
semantic_observe
{ "id": "cart.total" }Resolves [data-semtag-id="<id>"] and returns its compact current state. Returns a
clear error if zero or multiple elements match.
Setup
cd semantic-hints-mcp
npm install
npx playwright install chromium # one-time browser download
npm run buildSee Configuration above for the environment variables.
Register in Claude Code
From this folder, after npm run build:
claude mcp add semantic-hints \
--env SEMANTIC_HINTS_CDP_URL=http://127.0.0.1:9222 \
--env SEMANTIC_HINTS_TARGET_URL=http://localhost:8080 \
-- node "$(pwd)/dist/index.js"Or add it to .mcp.json / your Claude Code config manually:
{
"mcpServers": {
"semantic-hints": {
"command": "node",
"args": ["/absolute/path/to/semantic-hints-mcp/dist/index.js"],
"env": {
"SEMANTIC_HINTS_CDP_URL": "http://127.0.0.1:9222",
"SEMANTIC_HINTS_TARGET_URL": "http://localhost:8080"
}
}
}
}(For development without a build step, use "command": "npx", "args": ["tsx", "/abs/path/src/index.ts"].)
Usage example
Expected Claude Code workflow:
Start the app under test, e.g.
npm run dev # → http://localhost:8080 for the annotated WebTestBench appStart a shared Chromium with CDP enabled, e.g.
chromium --remote-debugging-port=9222Register/start both MCP servers:
the official Playwright MCP, configured for the shared browser if possible
the semantic-hints MCP, configured with
SEMANTIC_HINTS_CDP_URL=http://127.0.0.1:9222
Agent calls:
semantic_snapshot({ "url": "http://localhost:8080" })Agent interacts using the official Playwright MCP:
browser_click({ "target": "[data-semtag-id='checkout.submit']" })Agent calls:
semantic_observe({ "id": "cart.total" })
The click (step 5) and observe (step 6) must operate on the same browser page/session — which is exactly what the shared CDP browser guarantees.
Tests
npm testVitest loads test/fixture.html in headless Chromium and checks: grouped compact
output, hidden-element handling, scope, observable/input value reads, missing &
duplicate-ID errors, and that output contains no raw HTML/DOM.
Available Tools
2 toolssemantic_observeSemantic observeA
Return the current compact value/state of a single hinted element, resolved via [data-agent-id='']. Returns a clear error if no element or multiple elements match. Use after a snapshot to read the live value/state of one element. Returns compact JSON only.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | The data-agent-id of the element to observe. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations provided, so description carries full burden. It discloses error conditions for no/multiple matching elements and that output is compact JSON only, which is sufficient for a simple read tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Description is concise with no fluff, front-loaded with the core purpose, and every sentence adds value.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's simplicity (single parameter, no output schema, no nested objects), the description covers purpose, usage context, error handling, and output format comprehensively.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema has 100% coverage including parameter description. Description adds clarity by specifying the attribute used to resolve the element and the single-element constraint, adding meaning beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool returns the current compact value/state of a single hinted element using a specific attribute, and differentiates from its sibling by specifying it is used after a snapshot.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly says 'Use after a snapshot to read the live value/state of one element', providing clear usage context. It does not explicitly state when not to use it, but the context implies the scope.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
semantic_snapshotSemantic snapshotA
Return a compact map of hinted UI elements (only elements carrying data-agent-id), grouped into regions/actions/inputs/observables/navigation/other. Use this as a cheap first observation instead of a full accessibility snapshot. To act on an element, pick its id and use the Playwright MCP with selector [data-agent-id='']. Returns compact JSON only — never HTML, DOM, or class names.
| Name | Required | Description | Default |
|---|---|---|---|
| url | No | Optional URL to open before extracting. If omitted, reads the current page. | |
| scope | No | Optional data-agent-id or CSS selector to limit extraction to a subtree. | |
| includeHidden | No | Include hidden elements (default false). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description fully discloses that the tool returns only compact JSON (no HTML/DOM/class names) and groups elements. It implies a read-only, cheap operation, which is sufficient for transparency.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is concise, front-loaded with the main purpose, and every sentence adds value. No wasted words.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given no output schema, the description adequately explains the output format (compact JSON with groupings, excludes HTML/DOM/class names) and how to use the returned ids. It is complete for a lightweight observation tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so baseline is 3. The description does not add meaning beyond what the schema already provides for each parameter (url, scope, includeHidden).
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool returns a compact map of hinted UI elements grouped into categories, and distinguishes it from a full accessibility snapshot. However, it does not explicitly differentiate from its sibling tool 'semantic_observe', which could be more detailed.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description advises using this as a cheap first observation before a full snapshot, and instructs the user to pick an `id` and use Playwright MCP for actions. It provides clear context and next steps but lacks explicit when-not-to-use conditions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.
2 tool updates
v0.1.0- First observed
semantic_observe - First observed
semantic_snapshot
TDQS
The two tools have clearly distinct purposes: semantic_snapshot provides an overview of all hinted elements, while semantic_observe retrieves the state of a single element. No ambiguity.
Both tools follow a consistent verb_noun pattern with the 'semantic_' prefix, using snake_case. The naming is predictable and clear.
With only 2 tools, the server is tightly scoped to its purpose of observing semantic hints. This is appropriate for a focused utility; each tool earns its place.
The tool surface covers the essential operations for the domain: getting a full map of elements and reading a single element's state. No obvious gaps for an observation-only tool.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
MCP server for visual regression testing: triage a PR's UI diffs from your coding agent.
9118Hosted browser for AI agents: screenshots, post-JS DOM, console, WCAG. No install, no API key.
61Live browser debugging for AI assistants — DOM, console, network via MCP.
Agent-native MCP server over the public saagarpatel.dev corpus. Read-only, stateless.
Related MCP Servers
- AlicenseNot gradedqualityNot gradedmaintenanceA Playwright-based MCP server that exposes a live browser as a traceable, inspectable, debuggable and controllable execution environment for AI agents.5,21857-
- AlicenseAqualityAmaintenancesemantic browser observation for AI agents via MCP, 100% local, zero telemetry12227MIT
- AlicenseAqualityCmaintenanceAgent-native browser control MCP server that enables AI agents to browse and interact with web pages via accessibility tree snapshots and ref ID-based commands.152MIT
- AlicenseNot gradedqualityCmaintenanceGives AI agents and MCP clients direct control over native desktop apps, Chrome/Electron browsers, and Android devices with screenshots, OCR, accessibility-based element lookup, input simulation, window management, CDP, and ADB in one local server.128MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/timotheeiss/semantic-hints-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server