Why did the test fail? Let the AI assistant look for itself
A Cypress test fails and you want an AI assistant to help. Everyone knows what comes next: copy the error from the terminal, paste it into the chat, the assistant asks what the page looked like, you go looking for the screenshot, and round it goes. The assistant can write a test, but it cannot see what happened when the test ran.
That is why I wrote cypress-mcp, a small MCP server through which the assistant reads the results of the last run by itself.
What exists already, and where it stops
Cypress has two tools of its own for AI assistants:
cypress tapgives the assistant a livecypress opensession. It fits the moment you are writing a test. It does not handle headlesscypress run, and so not CI either.- Cypress Cloud MCP reads runs recorded to Cypress Cloud. It needs a Cloud account and never sees a local run.
A gap remains: the test runs headless, on your machine or in a pipeline, without the cloud. That is exactly where tests fail most often.
Setup
Three steps. Install:
npm install -D @miroslavmyrha/cypress-mcp
Wire in the plugin and the support file:
// cypress.config.ts
import { defineConfig } from 'cypress'
import { cypressMcpPlugin } from '@miroslavmyrha/cypress-mcp/plugin'
export default defineConfig({
e2e: {
setupNodeEvents(on, config) {
cypressMcpPlugin(on, config)
},
},
})
// cypress/support/e2e.ts
import '@miroslavmyrha/cypress-mcp/support'
And tell the assistant where the server is. In Claude Code:
claude mcp add cypress -- npx -y @miroslavmyrha/cypress-mcp --project /path/to/project
From then on, every cypress run records the command log of each test, and for a test that failed also the state of the page.
What it looks like
Take a test that looks for a counter of finished items:
it('shows a counter of finished items', () => {
cy.get('.todo-list li').first().find('input[type=checkbox]').check()
cy.get('[data-test=finished-count]').should('contain', '1')
})
The test fails. The assistant calls the tool get_last_run and gets this (shortened):
{
"title": "Todo list > shows a counter of finished items",
"state": "failed",
"commands": [
{ "name": "visit", "message": "https://example.cypress.io/todo" },
{ "name": "get", "message": ".todo-list li" },
{ "name": "first", "message": "" },
{ "name": "find", "message": "input[type=checkbox]" },
{ "name": "check", "message": "" },
{ "name": "get", "message": "[data-test=finished-count]" }
],
"error": "AssertionError: Timed out retrying after 2000ms: Expected to find element: `[data-test=finished-count]`, but never found it."
}
So it sees which commands ran and where they stopped. Then it looks into the page as it was at the moment of failure. With query_dom it asks for the selector .todo-count:
{
"selector": ".todo-count",
"totalMatches": 1,
"results": [
{
"breadcrumb": "body > section.todoapp-wrapper > div.todoapp > footer.footer > span.todo-count",
"outerHTML": "<span class=\"todo-count\"><strong>1</strong> item left</span>"
}
]
}
The counter is on the page, only under another selector and with another text than the test expected. The assistant fixes the test, and nobody had to paste anything into the chat.
What else the server does
list_specsandread_speclist and read the spec files.get_screenshotreturns the screenshot of the failure as an image.run_specruns one spec file and waits for the result.get_configreads the Cypress configuration.
Two notes on safety
Test results contain text from the application under test, and that text can be anything, including instructions aimed at the assistant. The server therefore marks everything that comes from the page as untrusted data. Values that look like passwords or tokens are redacted before they are sent.
When to use which
You are writing a test with Cypress open: cypress tap. You record runs to Cypress Cloud: Cloud MCP. The tests run headless and you want to know why one failed: cypress-mcp.
The server is MIT licensed, developed and tested with Cypress 13.