easy-ui-mcp
easy-ui-mcp is a local, Dockerized MCP server that lets an AI agent drive Playwright browser UI tests and produce JSON/HTML reports with screenshots and browser-problem logs.
Start/end browser sessions, up to 3 per connection, with optional device, viewport, locale, timezone, and color scheme; idle sessions time out after 10 minutes.
Navigate, go back/forward/reload, and manage browser tabs.
Interact with pages: click, double/triple/right/middle-click with modifiers, click outside modals, fill/type, select options, press keys, hover, check boxes/radios, scroll, drag and drop, upload the built-in test PNG, and answer native dialogs.
Inspect state: get visible interactive elements, overlays and focused element; read text; retrieve console errors, page errors, failed requests and HTTP 4xx/5xx; capture viewport, element, full-page or inline screenshots.
Label steps, run hard assertions, soft checks, and waits; hard failures fail the session, while soft checks are recorded without failing.
End sessions to generate shareable JSON + self-contained HTML reports containing screenshots, step labels, pass/fail status, action log, and browser problems.
Click on "Deploy 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., "@easy-ui-mcp Go to example.com and take a screenshot"
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.
EASY UI MCP
Not just another browser controller — a local UI-testing MCP that gives your agent structured, shareable test reports.
easy-ui-mcp is a Dockerized MCP (Model Context Protocol) server for local UI testing. It exposes Playwright-based browser tools over HTTP/SSE so an AI agent (like Claude Code) can drive web UI flows step-by-step and get back a JSON + HTML report with screenshots — no server-side LLM, no test scripts to write.
The detailed guide is on the docs page (source: site/index.html, plain static files on Vercel).
The problem
General browser automation answers "can the agent click this?". Testing asks "did the flow work, and where is the proof?". A transcript of clicks is not proof: it has no verdict, no screenshots, and no list of the console errors and failed requests that happened along the way.
Related MCP server: Playwright MCP Server
Why use it
What you get | How it works |
A report for every session | A JSON file and a self-contained HTML report: screenshots, step labels, pass/fail, the action log. See What the report shows. |
Primitives made for testing |
|
A verdict you can trust | A hard failure fails the session; a soft |
Local-first, one command |
|
No LLM inside the server | The server only runs tools and records the result. The agent does the reasoning. |
Made for Claude Code | One |
Fewer wasted calls |
|
Report size was measured too: a soft-check run went from 357 KB to about 4 KB once soft failures stopped capturing a full-screen PNG. See Measured.
Quick Start
docker compose up -d --build
curl http://localhost:8765/health
# {"status":"ok"}
claude mcp add --transport http easy-ui-mcp http://localhost:8765/mcpThen ask Claude Code to navigate to a page and take a screenshot. Using it from another repo? Run claude mcp add from that repo's root too (guide).
Tools: ui_start_session, ui_end_session, ui_step, ui_navigate, ui_click, ui_fill, ui_select_option, ui_press_key, ui_hover, ui_set_checked, ui_scroll, ui_set_viewport, ui_type, ui_navigate_history, ui_click_outside, ui_dialog, ui_assert, ui_check, ui_wait_for, ui_get_page_state, ui_get_text, ui_get_logs, ui_tabs, ui_upload_file, ui_drag, ui_take_screenshot — plus a REST wrapper at POST /api/run-test. Details: Tools. With several open sessions, pass session_id to pick one (Several sessions).
Over stdio (Glama, mcp-proxy): build, then mcp-proxy -- node dist/stdio.js. See Running over stdio.
More
Networking and Scope (v1) are on the docs page. Also: AGENTS.md (architecture, full MCP connection guide), HARNESS.md (REST API), RUNBOOK.md (deploy and rollback), PRD.md (product intent).
Available Tools
26 toolsui_assertui_assertA
Assert a JS expression against the current page. This is a HARD check: if it evaluates false the whole session is marked failed. Use it only for claims about the application under test. To wait for the page to become ready use ui_wait_for, and for a non-fatal observation use ui_check — do not call ui_assert in a retry loop, as the first false result permanently fails the run.
Inputs: condition (required), session_id.
ui_checkui_checkA
Check a JS expression against the current page WITHOUT failing the session if it is false. Use this for observations you want recorded in the report but which should not condemn the run. If the expression cannot run at all (no page open, or it throws) that is still a hard failure.
Inputs: condition (required), session_id.
ui_clickui_clickA
Click the single element matching the given selector. The selector may be a CSS selector or the selector value from ui_get_page_state, passed unchanged. Selectors containing "nth=" are positional: call ui_get_page_state again after the page changes before reusing them. Optional: button (right/middle), clickCount 2 for a double-click, modifiers for Shift/Control-click, position for an offset inside the element, force to click through an element covering it. If the click opens a native alert/confirm/prompt, the reply says the browser is waiting: answer it with ui_dialog.
Inputs: selector (required), force, button, position, +3 more.
ui_click_outsideui_click_outsideA
Click just outside the single element matching the selector, the way a user closes a modal, drawer or popover by clicking its backdrop. Picks a point whose topmost element is not inside the target and not a control, and says what it hit. Pass the dialog panel, not the backdrop. Fails if no safe point exists (the element fills the viewport): use ui_press_key with Escape then. Selectors containing "nth=" are positional: call ui_get_page_state again after the page changes before reusing them.
Inputs: selector (required), session_id.
ui_dialogui_dialogA
Answer a native browser dialog (alert, confirm, prompt, beforeunload). When a tool call opens one, its reply says "Browser is waiting on a dialog" and every other tool is refused until you answer here. accept = OK / Leave; dismiss = Cancel / Stay; for a prompt add text. The reply also reports what the interrupted action did next. With no dialog waiting, this pre-sets the answer for the NEXT dialog, which is then answered at once (one answer, used once). An unanswered dialog is dismissed after a safety timeout.
Inputs: action (required), text, session_id.
ui_dragui_dragA
Drag the single element matching from onto the single element matching to with real pointer events: reorders HTML5 drag-and-drop lists, kanban cards and pointer-based sortables. Use the selector values from ui_get_page_state. Selectors containing "nth=" are positional: call ui_get_page_state again after the page changes before reusing them.
Inputs: to (required), from (required), session_id.
ui_end_sessionui_end_sessionA
End a session bracket: writes a JSON + HTML report and returns their paths. Pass session_id to choose which one; it can be omitted only when exactly one session is open.
Inputs: session_id.
ui_fillui_fillA
Fill the single input element matching the given selector with a value. The selector may be a CSS selector or the selector value from ui_get_page_state, passed unchanged. Selectors containing "nth=" are positional: call ui_get_page_state again after the page changes before reusing them.
Inputs: value (required), selector (required), session_id.
ui_get_logsui_get_logsA
Read the browser problems captured in this session so far: console errors, uncaught page errors, failed requests and HTTP 4xx/5xx responses (method + URL, never bodies or headers). Use it when an action seemed to do nothing. since: last (default) returns only what is new since the previous call; start returns everything. Pass level: warning to include console warnings. Repeats are grouped (×N); at most 50 lines.
Inputs: level, since, session_id.
ui_get_page_stateui_get_page_stateA
Return the current page's URL, title, visible interactive elements and visible alert/status messages. Each element is {role, name, selector, state?}: pass selector unchanged to ui_click/ui_fill (at most 200 elements); state lists only non-default fields (disabled, checked, value, expanded, selected, pressed, required, invalid; a password's value is never returned). Custom widgets (combobox, listbox, option, switch, slider, contenteditable, summary...) are listed too. overlays ({role, name, selector, modal}, topmost first) names each open dialog and is absent when none is open; focused ({role, name, selector}) names the element holding focus and is absent when focus is on the page body. To read page text use ui_get_text.
Inputs: session_id.
ui_get_textui_get_textA
Read the trimmed visible text of the single element matching the selector, or of the whole page body when no selector is given (cut at 4000 characters with a note of the real length). Use it to check content: a table value, a heading, an error under a field, a total. Fails if the selector matches no element, more than one, or an element that is not visible. Selectors containing "nth=" are positional: call ui_get_page_state again after the page changes before reusing them.
Inputs: selector, session_id.
ui_hoverui_hoverA
Move the pointer over the single element matching the selector, to reveal :hover or mouseenter content such as dropdown menus. Use the selector from ui_get_page_state. Selectors containing "nth=" are positional: call ui_get_page_state again after the page changes before reusing them.
Inputs: selector (required), session_id.
ui_press_keyui_press_keyA
Press a key (Playwright key name such as Enter, Tab, Escape, ArrowDown, or a combo like Control+a). With selector it presses on that element; without, on whatever has focus. Enter in a text input submits its form. Selectors containing "nth=" are positional: call ui_get_page_state again after the page changes before reusing them.
Inputs: key (required), selector, session_id.
ui_scrollui_scrollA
Scroll one step. With selector, bring that element into view. Otherwise scroll by one viewport height (or width) in direction (down, up, left, right, or jump to top / bottom, default down): the page, or with container the scroll area matching that selector (a chat panel, a virtual table, a carousel). The result says when the end was reached (nothing more to scroll that way): stop looping then. A page or panel with nothing to scroll is not an error. Selectors containing "nth=" are positional: call ui_get_page_state again after the page changes before reusing them.
Inputs: selector, container, direction, session_id.
ui_select_optionui_select_optionA
Choose an option of the single matching the selector, by option value or visible label. For a this selects exactly that one option. Use the selector from ui_get_page_state. Selectors containing "nth=" are positional: call ui_get_page_state again after the page changes before reusing them.
Inputs: value (required), selector (required), session_id.
ui_set_checkedui_set_checkedA
Set the single checkbox or radio button matching the selector to checked or unchecked. Idempotent: already in that state is fine. A radio cannot be unchecked directly. Use the selector from ui_get_page_state. Selectors containing "nth=" are positional: call ui_get_page_state again after the page changes before reusing them.
Inputs: checked (required), selector (required), session_id.
ui_set_viewportui_set_viewportA
Resize the current page's viewport (320-3840 x 320-2160 CSS pixels), e.g. 375x812 for a phone or 768x1024 for a tablet. Layouts can change when the page is resized, so call ui_get_page_state again afterwards.
Inputs: width (required), height (required), session_id.
ui_start_sessionui_start_sessionA
Start a session bracket: opens a fresh browser context and begins logging subsequent primitive tool calls into a report. A connection can hold several sessions at once (up to MAX_SESSIONS_PER_CONNECTION, default 3): pass the returned session id as session_id to every other tool to choose which session it acts on. A session left unused for 10 minutes is closed automatically and its report is written with status timed_out. Optionally test at the sizes and settings real users have: device (a Playwright device name such as "iPhone 13": mobile user agent, touch, pixel ratio and size), viewport (overrides the device size), colorScheme, locale, timezoneId. With none of them the browser is a 1280x720 desktop. These apply to this session only; use ui_set_viewport to resize mid-flow.
Inputs: target (required), device, locale, viewport, +2 more.
ui_stepui_stepA
Label what you are about to do, in plain language. Every following action is grouped under this label in the report until the next ui_step. Call it before each meaningful part of the flow (e.g. "Log in as jcarlin", "Toggle Manual Invoice access on", "Reload and confirm it persisted") — this label is the only human-readable description the report has, so write it for the person who will read the result, not for yourself.
Inputs: label (required), session_id.
ui_tabsui_tabsA
Manage browser tabs of this session (numbered from 1). A link with target=_blank or window.open opens a new tab, announced on the next tool response but not switched to. list: index, url, title, current. switch: make that tab the one every tool acts on. close: close that tab (default: the current one) and fall back to the previous one; the last tab cannot be closed.
Inputs: action (required), index, session_id.
ui_take_screenshotui_take_screenshotA
Capture the current viewport and save it as a PNG to the reports output. With selector, capture just that element; with fullPage, the whole scrollable page (cut at 16000 device pixels tall and 3840px wide). With inline: true the image itself is also returned (scaled to fit 1280x8000px; the file on disk stays full size) so you can look at it. Selectors containing "nth=" are positional: call ui_get_page_state again after the page changes before reusing them.
Inputs: inline, fullPage, selector, session_id.
ui_typeui_typeA
Type text into the single editable element matching the selector one key at a time, like a user (keydown, input and keyup per character), appending to what is already there. Use it instead of ui_fill for autocomplete, search-as-you-type, masked inputs (phone, card) and OTP boxes. Use the selector from ui_get_page_state. Text typed into a password, one-time-code or card-number field is never stored in the report. Selectors containing "nth=" are positional: call ui_get_page_state again after the page changes before reusing them.
Inputs: text (required), selector (required), delayMs, session_id.
ui_upload_fileui_upload_fileA
Upload the built-in test file default_draft.png (a tiny PNG) to the single element matching the selector: an input[type=file] (hidden ones work), or a button/label whose click opens the file chooser. Takes no path: this is the only file it can ever upload. If the input's accept excludes images the response says so. Selectors containing "nth=" are positional: call ui_get_page_state again after the page changes before reusing them.
Inputs: selector (required), session_id.
ui_wait_forui_wait_forA
Poll a JS expression until it becomes true or the timeout elapses. Use this to wait for the page to render instead of retrying ui_assert. Returning true is recorded as one action; timing out fails the session.
Inputs: condition (required), timeoutMs, session_id.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 26 tools
Most tools target a distinct primitive (navigate, click, hover, drag, scroll, upload, screenshot, tabs, dialog), and the descriptions actively disambiguate the tricky pairs. The remaining overlaps are mild but real: ui_fill vs ui_type both write into an input, and ui_assert vs ui_check vs ui_wait_for all evaluate JS, so an agent must read carefully to pick correctly.
Every tool is `ui_` prefixed snake_case with a verb-led name (ui_get_page_state, ui_set_checked, ui_press_key, ui_take_screenshot), including the session bracket pair ui_start_session/ui_end_session. No camelCase or naming-style drift anywhere in the set.
26 tools is heavy for a single server and sits at the top of the range where consolidation starts to pay off — tab list/switch/close are one ui_tabs tool, but viewport, history and click_outside could plausibly fold into existing primitives. Each tool is nonetheless a genuine, non-redundant browser primitive, so it is justified rather than padded.
Coverage of the core browser-automation lifecycle is strong: sessions, navigation/history, full input primitives, dialogs, tabs, uploads, waiting, assertions, console/network log reading and screenshots. Notable gaps remain around iframe/frame switching, cookie and localStorage inspection, and download handling, which would block some real flows.
Maintenance
Related MCP Connectors
Browser-based QA for AI-built software. Test pages with real browsers via agents.
Playwright browser automation — navigate, click, fill forms, extract, screenshot — 17 agent tools.
Write, run and check natural-language browser tests of your web app from your AI assistant.
Hosted browser for AI agents: screenshots, post-JS DOM, console, WCAG. No install, no API key.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables AI assistants to control browser automation through natural language prompts using Playwright, supporting visual element interaction, PDF generation, screenshots, and testing assertions.ISC
- AlicenseNot gradedqualityDmaintenanceEnables web browser automation and inspection using structured data instead of screenshots, allowing AI agents to interact with web pages programmatically through the Playwright framework.ISC
- AlicenseNot gradedqualityDmaintenanceEnables AI assistants to control web browsers through Playwright automation, providing 50+ tools for navigation, interaction, testing, accessibility audits, and visual testing across Chromium, Firefox, and WebKit.24 npmMIT
- AlicenseNot gradedqualityDmaintenanceEnables AI assistants to execute browser automation, perform QA tasks, and generate test code through natural language commands using Playwright.7MIT