MCP server
A local MCP server that lets your coding agent read your Squally data: which runs happened, how each test behaves across them, and why a test failed.
What it is
Section titled “What it is”squally-mcp is a local MCP server. It runs on your machine over stdio, started
by your coding agent, and reads your Squally data through the
read API over HTTPS - the same requests a curl would
make, answered with the same JSON.
Read-only by construction. The server issues HTTP GET requests and nothing
else: there is no code path that writes. Every tool is annotated
readOnlyHint: true, which clients may use to skip the confirmation - Claude
Desktop still asks for each call in its manual mode, and Always allow on a
tool removes those prompts. It cannot reach past your organization either: the
key is scoped to one organization, and scoping, plan checks and secret
exclusion are enforced by the API, not by this client.
The package is squally-mcp on npm,
MIT-licensed, with its source on GitHub.
Requirements
Section titled “Requirements”- A Standard or Pro plan. The read API is part of both; on Free, every call
is answered
tier_too_low. - An organization read key. An organization admin creates it under
Organization › Read keys → Create read key. It starts with
sqly_ro_and is shown once. It is not the project ingest key your Playwright reporter uses - the server refuses an ingest key and says that it is one. More in Read keys. - Node 22 or newer. Your agent starts the server with
npx. - A version that matches the read API.
app.squally.devmoved to read API 1.0.0-beta.18 on 23.10.2026; which version it serves now is in the API changelog. squally-mcp 0.8.0 and later work against beta.17 and beta.18; against beta.18 its test and error tools count CI and local runs unless you passsource. Versions before 0.7.0 expect a project’s stable branch, which beta.17 removed: a client that validates the tools’ structured output - the MCP SDK’s does - rejects the answers ofsqually-list-projectsandsqually-find-run, so update to the latest version.
What an agent can see
Section titled “What an agent can see”Worth knowing before you hand a key to an assistant. Through this server an agent can read:
- your projects and their names;
- your runs - CI and local ones, each marked - with commit SHA and message, branch, author, pull request, timing, the run’s status and counts, and how a run was cancelled; since read API 1.0.0-beta.15 also its job key, its attempt, the run it re-ran and the link to its CI run;
- per-test results for a run, including which shard ran what and whether an attempt ran into its time limit;
- for a failing attempt: the error message and stack, the error snippet - verbatim source lines from your test file - and the page snapshot at the moment of failure: an ARIA snapshot of your application, whatever was on screen, truncated to 3000 characters. All three are part of the Copy-for-AI prompt, which the agent gets too;
- per-test metrics, counted per run - a sharded run once, when all its shards have finished; CI and local runs by default, or one of them when asked: how often each test passed on the first try, passed only after a retry, or failed, and how much time its retries and failures cost - see How Squally measures tests;
- error signatures: failures grouped across runs and branches, with how many runs each one hit and how those runs ended for its tests.
It cannot see screenshots, videos or traces - artifacts are deliberately out of scope - and it cannot see any key, secret or webhook URL.
Each client starts the server with npx -y squally-mcp and hands it your key
as SQUALLY_API_KEY. Replace sqly_ro_your_key_here with your read key. If
the client’s config file already has an mcpServers section, add the
squally entry to it.
Claude Code
Section titled “Claude Code”For one project, a .mcp.json at the project’s root - it can be committed,
because it holds a reference to the key, not the key:
{ "mcpServers": { "squally": { "command": "npx", "args": ["-y", "squally-mcp"], "env": { "SQUALLY_API_KEY": "${SQUALLY_API_KEY}" } } }}Claude Code fills in ${SQUALLY_API_KEY} from the environment it starts in,
so set the variable there - in your shell profile or from a secrets manager.
The key then stays out of every file and out of your shell history. The same
entry from the command line, in single quotes so your shell writes the
reference and not the key:
claude mcp add squally --scope project \ --env 'SQUALLY_API_KEY=${SQUALLY_API_KEY}' \ -- npx -y squally-mcpClaude Code asks once before it starts a server from a project’s
.mcp.json. For all your projects instead, use --scope user: the entry then
lives in ~/.claude.json, outside any repository. claude mcp list shows
whether it connected; /mcp shows it inside a session.
On Windows (not WSL), Claude Code starts npx through cmd: write
"command": "cmd", "args": ["/c", "npx", "-y", "squally-mcp"] in .mcp.json,
or end the command above with -- cmd /c npx -y squally-mcp.
In ~/.codex/config.toml - the Codex CLI and the IDE extension read the same
file:
[mcp_servers.squally]command = "npx"args = ["-y", "squally-mcp"]
[mcp_servers.squally.env]SQUALLY_API_KEY = "sqly_ro_your_key_here"Or the same from the command line:
codex mcp add squally \ --env SQUALLY_API_KEY=sqly_ro_your_key_here \ -- npx -y squally-mcpcodex mcp list shows the configured servers; /mcp in the Codex TUI shows
the active ones.
Cursor
Section titled “Cursor”In ~/.cursor/mcp.json, for all your projects:
{ "mcpServers": { "squally": { "type": "stdio", "command": "npx", "args": ["-y", "squally-mcp"], "env": { "SQUALLY_API_KEY": "sqly_ro_your_key_here" } } }}A project’s .cursor/mcp.json works too, but it is meant to be committed - so
write "SQUALLY_API_KEY": "${env:SQUALLY_API_KEY}" there instead of the key,
and set the variable in the environment Cursor starts from. Save the file and
restart Cursor; the server appears under Customize → MCPs, where it can be
switched on and off. The Cursor CLI reads the same files.
Claude Desktop
Section titled “Claude Desktop”In Claude Desktop, open Settings → Developer and click Edit Config to
find claude_desktop_config.json - on macOS in
~/Library/Application Support/Claude/, on Windows in %APPDATA%\Claude\.
Quit Claude Desktop completely, including from the system tray, before you
edit it - a running app overwrites your edit. Then add:
{ "mcpServers": { "squally": { "command": "npx", "args": ["-y", "squally-mcp"], "env": { "SQUALLY_API_KEY": "sqly_ro_your_key_here" } } }}Then start Claude Desktop again - it loads the configuration when it starts.
On Windows, npx can fail to start when npm is not installed globally. If
%APPDATA%\npm does not exist, run npm install -g npm once.
As of squally-mcp 0.8.4, read from the package when these docs were built. Each tool is
one call on the read API; the path links to its reference.
| Tool | What it answers | Cost |
|---|---|---|
squally-list-projectsGET | Which projects exist. Start here — every other tool needs a projectId from it. | cheap |
squally-find-runGET | Which runs happened — latest, or by branch, commit SHA, status or source. Counters only. | cheap |
squally-get-runGET | One run with its per-test rows, across all shards. Which test is red. | cheap |
squally-debug-failureGET | Every attempt of one test in one run: error, stack, full-context prompt. | cheap |
squally-get-test-metricsGET | One test's stability, flaky rate and failure rate over a period. | cheap |
squally-list-testsGET | Every test with a run in the period, ranked by stability, flaky rate or failures. | cheap |
squally-list-errorsGET | Error signatures in a period: what keeps failing, grouped, and how its runs ended. | cheap |
Which runs the tools count
Section titled “Which runs the tools count”squally-list-tests, squally-get-test-metrics and squally-list-errors
take a source argument (since 0.4.0):
all- both, the default since squally-mcp 0.8.0 (read API 1.0.0-beta.18); before, the default wasci;ci- CI runs only: send it to read your CI on its own;local- local runs, runs no shard reported a CI provider for.
Every answer names the runs it counted in population. The test tools leave
out runs that were a mass failure as a whole; squally-list-errors counts
them. A run still in progress counts once all its shards have finished - or,
once the run timeout has passed, with
the results its shards reported, also when a shard hit the monthly result
limit (read API 1.0.0-beta.10 and beta.11). The values:
Conventions.
Finding runs
Section titled “Finding runs”squally-find-run lists runs of every source by default (since 0.5.0): it
counts nothing, it says what ran. Its source argument narrows it to ci or
local. Every run it returns, and the run of squally-get-run, says:
status- the run page’s word:passed,failed,cancelled,incompleteorrunning. Thestatusfilter ofsqually-find-runtakes all five; a cancelled run matchescancelledonly. See Run summary.local-truewhen no shard of the run reported a CI provider.- the counts - every test once, however many shards reported it, by its final attempt across all of them; the tests a cancel cut off count nowhere.
The cancelled boolean of 0.4.0 is gone: it repeated
status === "cancelled". cancellation says where a cancel came from and
when.
Since read API 1.0.0-beta.15 the same runs carry five more fields, which squally-mcp 0.6.0’s tool descriptions and output schemas name:
settled-truewhen the run’s status and counts are final and it counts in the dashboard’s figures. Anincompleterun that is not settled still waits for a shard within the run timeout and can still change - see Incomplete: waiting or settled.jobKey- the job key the reporter sent (SQUALLY_CI_JOB_KEY, squally-reporter 0.11.0), ornull. Each job with a key is its own run.attempt- which attempt of its CI run this is,2after GitHub’s Re-run failed jobs;nullwhen it is not known.rerunOf-{ id, runNumber }of the run this one re-ran, ornull.ciRunUrl- the CI run’s page, the run page’s View in CI link, ornull.
Examples
Section titled “Examples”A tool’s answer is the read API’s JSON, unchanged. The answers below are the API’s own examples, with invented data.
squally-list-tests
Section titled “squally-list-tests”The three least stable tests of the last 30 days - the default period and sort:
{ "projectId": "3f7c2a91-5b8e-4d0a-9c61-2e4b7f0d8a15", "perPage": 3 }The first of the three rows in the answer:
{ "testName": "chromium > checkout.spec.ts > Checkout > applies a coupon code", "filePath": "tests/checkout.spec.ts", "browser": "chromium", "runs": 8, "stableRuns": 4, "flakyRuns": 4, "failedRuns": 0, "stability": 0.5, "flakyRate": 0.5, "failureRate": 0, "timeLostMs": 7361, "recentResults": [ { "runId": "5c8e2a17-9d4b-4f60-b3a1-7e2d9c0f4b58", "runNumber": 1261, "startedAt": "2026-09-12T07:30:41.000Z", "branch": "main", "result": "stable" }, { "runId": "c2e7a940-5d1b-4f8e-9a36-0b4d7e1c5f92", "runNumber": 1264, "startedAt": "2026-09-14T15:12:09.000Z", "branch": "feature/saved-carts", "result": "flaky" }, { "runId": "a3f19c62-0e7d-4b85-9c24-6d1e8b3f7a09", "runNumber": 1266, "startedAt": "2026-09-15T07:31:03.000Z", "branch": "main", "result": "flaky" }, { "runId": "d07b4e95-6a2c-4e13-8f5d-2b9c1a7e6f30", "runNumber": 1270, "startedAt": "2026-09-17T07:29:58.000Z", "branch": "main", "result": "flaky" }, { "runId": "6b1f8d23-9e4a-4c07-b5d2-3a8e0f6c1d74", "runNumber": 1273, "startedAt": "2026-09-18T11:47:35.000Z", "branch": "feature/saved-carts", "result": "stable" }, { "runId": "8f6a3d21-4c9e-4a7b-b052-e1d7c3a9f864", "runNumber": 1277, "startedAt": "2026-09-20T07:32:16.000Z", "branch": "main", "result": "stable" }, { "runId": "7d2f4b19-3c8e-4a61-9e07-5b1d8c2a6f34", "runNumber": 1283, "startedAt": "2026-09-23T16:05:40.000Z", "branch": "feature/saved-carts", "result": "stable" }, { "runId": "b1e0c6d2-7a43-4f19-8d5e-0c3a9e6f2b71", "runNumber": 1284, "startedAt": "2026-09-24T07:31:12.000Z", "branch": "main", "result": "flaky" } ], "branches": [ "feature/saved-carts", "main" ], "topBranch": { "branch": "main", "kind": "retries", "count": 3 }}Around the rows, the answer names the period, the branch and browser filters, the sort and the page. List tests describes every field.
squally-get-test-metrics
Section titled “squally-get-test-metrics”One test, by the name Squally shows for it:
{ "projectId": "3f7c2a91-5b8e-4d0a-9c61-2e4b7f0d8a15", "testName": "chromium > checkout.spec.ts > Checkout > applies a coupon code"}The answer - the numbers of that test’s row, beside the period they cover:
{ "project": { "id": "3f7c2a91-5b8e-4d0a-9c61-2e4b7f0d8a15", "name": "storefront-web" }, "population": "ci_groups_finished_not_mass_failure", "days": 30, "since": "2026-08-25T08:00:00.000Z", "until": "2026-09-24T08:00:00.000Z", "branch": null, "testName": "chromium > checkout.spec.ts > Checkout > applies a coupon code", "filePath": "tests/checkout.spec.ts", "browser": "chromium", "runs": 8, "stableRuns": 4, "flakyRuns": 4, "failedRuns": 0, "stability": 0.5, "flakyRate": 0.5, "failureRate": 0, "timeLostMs": 7361, "recentResults": [ { "runId": "5c8e2a17-9d4b-4f60-b3a1-7e2d9c0f4b58", "runNumber": 1261, "startedAt": "2026-09-12T07:30:41.000Z", "branch": "main", "result": "stable" }, { "runId": "c2e7a940-5d1b-4f8e-9a36-0b4d7e1c5f92", "runNumber": 1264, "startedAt": "2026-09-14T15:12:09.000Z", "branch": "feature/saved-carts", "result": "flaky" }, { "runId": "a3f19c62-0e7d-4b85-9c24-6d1e8b3f7a09", "runNumber": 1266, "startedAt": "2026-09-15T07:31:03.000Z", "branch": "main", "result": "flaky" }, { "runId": "d07b4e95-6a2c-4e13-8f5d-2b9c1a7e6f30", "runNumber": 1270, "startedAt": "2026-09-17T07:29:58.000Z", "branch": "main", "result": "flaky" }, { "runId": "6b1f8d23-9e4a-4c07-b5d2-3a8e0f6c1d74", "runNumber": 1273, "startedAt": "2026-09-18T11:47:35.000Z", "branch": "feature/saved-carts", "result": "stable" }, { "runId": "8f6a3d21-4c9e-4a7b-b052-e1d7c3a9f864", "runNumber": 1277, "startedAt": "2026-09-20T07:32:16.000Z", "branch": "main", "result": "stable" }, { "runId": "7d2f4b19-3c8e-4a61-9e07-5b1d8c2a6f34", "runNumber": 1283, "startedAt": "2026-09-23T16:05:40.000Z", "branch": "feature/saved-carts", "result": "stable" }, { "runId": "b1e0c6d2-7a43-4f19-8d5e-0c3a9e6f2b71", "runNumber": 1284, "startedAt": "2026-09-24T07:31:12.000Z", "branch": "main", "result": "flaky" } ], "branches": [ "feature/saved-carts", "main" ], "topBranch": { "branch": "main", "kind": "retries", "count": 3 }}A test with results but no counted run of the source in the period answers
runs: 0 and null rates - an answer, not an error. When the name exists in several
spec files, the tool answers with the filePath values to repeat the call
with. What each number means: How Squally measures tests.
squally-list-errors
Section titled “squally-list-errors”The errors of the last 30 days, in CI runs and local runs together:
{ "projectId": "3f7c2a91-5b8e-4d0a-9c61-2e4b7f0d8a15", "source": "all" }One error from the answer:
{ "fingerprint": "9c1e5a7f3b2d8e4c6a0f1b9d7e3c5a2f8b4d6e0c1a9f7b3e5d2c8a4f6e0b1d3c", "title": "toHaveText · getByTestId('order-total')", "message": "Error: expect(locator).toHaveText(expected) failed Locator: getByTestId('order-total') Expected: \"€ 45.00\" Received: \"€ 50.00\" Timeout: 5000ms", "category": "Assertion Failures", "filePath": "tests/checkout.spec.ts", "fileCount": 1, "testCount": 1, "testNames": [ "chromium > checkout.spec.ts > Checkout > applies a coupon code" ], "occurrenceCount": 9, "runCount": 7, "failedCount": 5, "flakyCount": 2, "failedRunCount": 5, "flakyRunCount": 2, "massFailureRunCount": 0, "firstSeen": "2026-09-02T11:20:44.000Z", "lastSeen": "2026-09-24T07:34:05.000Z"}runCount- the runs the error appeared in, a sharded run once (since 0.4.0; it counted every shard).failedCountandflakyCount- of the runs and tests the error hit, how many ended failed and how many passed after a retry, by the test’s final attempt across the run’s shards.massFailureRunCount- how many of those runs were a mass failure.
Around the errors, the answer gives population, the period, and
unfinishedRuns - the runs that have not finished and are in no figure.
isNew is gone since 0.4.0: compare firstSeen with window.from instead.
List errors describes every field.
Troubleshooting
Section titled “Troubleshooting”The server stops at start: SQUALLY_API_KEY is not set. Before anything
else, the server checks for the key and, without one, exits with a single line
naming the variable. Your client shows that line in the server’s log - Claude
Desktop in mcp-server-squally.log (see Claude Desktop
below for the folder), Cursor in the Output panel under MCP Logs. Add the
key to the client’s config (Setup) and restart the server.
It refuses the key as an ingest key. A key that starts with sqly_ but not
sqly_ro_ is a project ingest key - the one your Playwright reporter uses to
upload results. The server refuses it at start, before any request, with a
message that SQUALLY_API_KEY looks like a project ingest key.
Create an organization read key under Organization › Read keys.
fetch failed. The request never reached the API. Since 0.1.3 the tool
error names the cause - for example UNABLE_TO_VERIFY_LEAF_SIGNATURE, a
certificate that could not be verified, or ENOTFOUND, a host name that did
not resolve - and the MCP log gets one line per failed call with the same
cause. The log’s first lines say which Node runs the server and whether it
trusts the operating system’s certificate store (Certificates).
tier_too_low. The organization is on Free, and the read API is part of
Standard and Pro. The agent gets the API’s sentence as a tool error and passes
it on, rather than reporting Squally as unreachable. Keys are not revoked on a
downgrade; they work again after an upgrade.
Rate limited. Over the limit, the tool answers: wait 60 seconds, then retry.
The API’s 429 says the same with Retry-After: 60 and the error
rate_limited; a 429 from the edge firewall, without either, means the same
wait. The numbers are on Rate limit.
Answers are rejected after an API update. When the client says a tool’s
structured output does not match its schema - for example
squally-find-run after the API moved to 1.0.0-beta.14 - the package is
older than the API it talks to. Update it: npx -y squally-mcp@latest, or
npm install -g squally-mcp for a global install. See
Requirements.
The agent calls squally-list-flaky-tests or squally-get-test-status.
Both were removed in 0.2.0, together with the API endpoints they read;
squally-list-tests and squally-get-test-metrics take their places. An agent
that still offers the old two runs an older copy of the package - a global
install keeps its version until you run npm install -g squally-mcp again.
A note that the key expires. From 7 days before your key expires, every
successful tool result carries one extra line with the date. Keys are not renewed: create
a new key, put it in the client’s config, and revoke the old one. After the
date, calls are answered key_expired.
Every other error reaches the agent as the API’s sentence, its code and what to do about it. All codes: the error table on Conventions.
Claude Desktop
Section titled “Claude Desktop”The server does not start in time. The first start through npx downloads
the package, which can take longer than Claude Desktop’s 60-second limit for a
server to start. Run npm install -g squally-mcp once beforehand, and again
after each update: npx then uses that global copy instead of downloading.
Your config edit is gone. A running Claude Desktop overwrites
claude_desktop_config.json. Edit it only while the app is fully closed - quit
it from the system tray too.
Where the log is. mcp-server-squally.log is in ~/Library/Logs/Claude on
macOS and %APPDATA%\Claude\logs on Windows. The Microsoft Store build also
writes to %LOCALAPPDATA%\Claude\logs.
Testing against a local app
Section titled “Testing against a local app”Set SQUALLY_API_URL next to SQUALLY_API_KEY to point the server at another
Squally, such as a local one: http://localhost:3000. It takes the base
only - the server appends /api/v1 itself, and tolerates a trailing / or
/api/v1. Without it, the server talks to https://app.squally.dev. Use a read
key created in that app: a key is only known to the app that issued it.
If the address is wrong, the tool answers that it could not reach the Squally API there.
Security
Section titled “Security”Certificates. squally-mcp trusts the operating system’s certificate store
in addition to Node’s own, like a browser does, so it works behind antivirus
HTTPS scanning and corporate TLS-inspecting proxies - on Node 22.19+ or 24.5+,
while an older Node works as before; SQUALLY_USE_SYSTEM_CA=0 turns it off.
The key stays on your machine, in your agent’s local config. The server
reads it from its environment, sends it only to the Squally API as the
Authorization header, and never prints it. Keep it out of anything you
commit: the user-level configs above keep it out of the repository.
The key reaches every project in the organization. Create one per machine, so a single revocation does not lock out everything, and revoke it in Organization › Read keys when you no longer need it. The list there shows when each key was created, last used, and when it expires.
Provenance. Every version from 0.1.1 on is published from the package’s GitHub Actions workflow with an npm provenance attestation: a signed statement linking the tarball to the workflow run and the commit it was built from. To check the latest version:
npm view squally-mcp dist.attestationsA version with provenance answers with the attestation’s url and
predicateType: 'https://slsa.dev/provenance/v1'; one without answers nothing.
0.1.0, published by hand before trusted publishing was set up, is the only
version without.