> Meticulous records real user sessions from a web app and replays them against each commit to catch visual regressions.
>
> Full AI-readable docs index (complete project setup journey + every page as markdown): https://app.meticulous.ai/llms.txt

# MCP server for agents

The Meticulous MCP server provides tools which enable agents to interface with Meticulous: get test run diffs, replay details, coverage, and more. It's a hosted [Model Context Protocol](https://modelcontextprotocol.io) endpoint, hosted at https://app.meticulous.ai/api/mcp. We furthermore provide [agent skills](/docs/agents/skills) which compose these tools into higher-level workflows, like a skill to review a PR test run.

- [What it's for](#what-its-for)
- [Connecting a client](#connecting-a-client)
- [Authenticating with a token instead](#authenticating-with-a-token-instead)
- [Available tools](#available-tools)
- [What this connector can access](#what-this-connector-can-access)
- [Troubleshooting](#troubleshooting)
- [Support and policies](#support-and-policies)

---

## What it's for

Meticulous records real user sessions in your app and replays them against every commit to catch visual regressions before they ship. This server lets an agent — reviewing a PR, debugging a failing check, or investigating coverage — pull that data directly: which screenshots changed and why, whether a diff is a real regression or noise, which lines of a change are covered by a test, and (for CI/build agents) trigger a new run against a build.

---

## Connecting a client

The server is an OAuth 2.1 protected resource — it supports dynamic client registration and standard discovery (`/.well-known/oauth-protected-resource`, `/.well-known/oauth-authorization-server`), so clients connect with just the endpoint URL above. There is no client id, secret, scope, or authorization-server URL to configure by hand. Login happens in your browser on first connect and tokens refresh automatically.

**Claude Code**

Run the following in your terminal:

```bash
claude mcp add --transport http Meticulous https://app.meticulous.ai/api/mcp
```

Then, in Claude Code, type `/mcp` and choose "Authenticate" for the Meticulous MCP.

**Cursor**

Add to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (per project):

```json
{
  "mcpServers": {
    "Meticulous": { "url": "https://app.meticulous.ai/api/mcp" }
  }
}
```

**Codex/ChatGPT**

Add a server with name "Meticulous" and URL `https://app.meticulous.ai/api/mcp`, then click "Authenticate".

Calls are scoped to your default project. If you only have access to a single project, that one is used automatically; otherwise set a default in your [user settings](/user-settings) in the web app, or via the CLI: `meticulous auth set-project`.

---

## Authenticating with a token instead

Any MCP client can also authenticate with a static bearer token instead of the browser OAuth flow — useful in CI, or wherever an interactive login isn't possible. The token is a Meticulous OAuth token or a project API token (see [Setup > Org-wide setup](/docs/agents/setup#org-wide-setup) for how to obtain one). A project API token scopes every call to that one project.

```json
{
  "url": "https://app.meticulous.ai/api/mcp",
  "headers": { "Authorization": "Bearer <token>" }
}
```

---

## Available tools

Every tool takes broadly the same arguments as its matching [CLI command](/docs/agents/cli-commands) and returns the same data as that command's `--json` output — with minor differences inherent to a hosted endpoint rather than a local CLI (for example, no git-inferred `commitSha`, since the server has no checkout to infer it from). "Access" below reflects each tool's MCP annotations, which determine whether a client can call it without per-call confirmation.

### Identity and project selection

| Tool | Access | What it does |
|---|---|---|
| `whoami` | Read | Show the identity the connection is authenticated as, and the project it resolves to. |
| `list_projects` | Read | List the projects the authenticated user can access. |
| `get_project` | Read | Show the project project-scoped tools use when not given a `project` argument, and where that came from. |
| `set_project` | Write | Change the default project for the user account — every session and machine, not just this connection. |

### Test runs and diffs

| Tool | Access | What it does |
|---|---|---|
| `get_test_run_for_commit` | Read | Look up the latest test run for a commit; returns its ID and status. |
| `get_test_run_diffs` | Read | Get the (curated or full) list of screenshot diffs for a test run. |
| `get_test_run_diffs_counts` | Read | Get aggregate diff counts for a test run, including the six-way review-decision breakdown. |
| `get_image_urls` | Read | Get signed URLs for a screenshot diff's before/after/diff images. |
| `get_dom_diff` | Read | Get the structural DOM diff for one screenshot diff, as unified-diff-style hunks. |
| `get_timeline_diff` | Read | Get the list of timeline event differences (e.g. network requests, DOM mutations) for a replay diff. |
| `get_test_run_check` | Read | Get the Markdown report for a builtin or custom non-visual check. |
| `get_test_run_check_available_ids` | Read | List the check IDs available for a test run, for the checks that have reported results so far. |

Computing a test run's diffs is asynchronous — the tool returns `{ status: 'processing' }` until done. A poll can itself start the underlying compute workflow; that lazy compute does not make the tool a write. A `failed` result (with a `reason`) means nothing is still computing — stop polling; a `test-run-not-ready` reason can be retried by calling again a minute or more later, the others cannot.

`get_test_run_check` is also asynchronous: it returns `{ status: 'processing' }` until the separately computed results arrive. Poll every 10s for at most 3 minutes — the same budget the CLI gives itself — and treat still-processing after that as "not reported yet" rather than polling on. A completed response is `{ status: 'complete', text }`, or `{ status: 'complete', text, url }` when the report is too large to return inline — `text` is then a short notice and the full report is downloadable from `url`. A `failed` result (with a `reason`) is final — there is no way to retry. With `checkType: 'custom'`, an error saying the run is not expecting custom check results can be transient shortly after the run completes, since your CI registers its checks separately from the run itself — retry for a minute or so before concluding the run has none. Use `get_test_run_check_available_ids` to find a valid `checkId` instead of guessing one.

### Reviewing diffs

| Tool | Access | What it does |
|---|---|---|
| `get_diff_comments` | Read | Get the review comments (with replies) for a screenshot diff, oldest first. |
| `reject_diff` | Write | Agent-reject a screenshot diff and comment why. |
| `ignore_diff` | Write | Agent-ignore a screenshot diff and comment why. |
| `create_diff_comment` | Write | Start a review comment thread at approximate image coordinates. |
| `reply_to_diff_comment` | Write | Reply to an existing review comment thread. |

### JS coverage

| Tool | Access | What it does |
|---|---|---|
| `get_test_run_js_coverage` | Read | Get per-file JavaScript coverage for a test run. |
| `get_project_js_coverage` | Read | Get per-file JavaScript coverage for a project's latest successful test run. |
| `get_replay_js_coverage` | Read | Get per-file JavaScript coverage for a single replay, or one screenshot of it. |
| `get_replay_diff_js_coverage_diff` | Read | Get per-file JavaScript coverage differences (base vs. head) for a replay diff. |

### Sessions

| Tool | Access | What it does |
|---|---|---|
| `get_sessions` | Read | List a project's most recently created recorded sessions, newest first. |
| `get_session_data` | Read | Get the recorded user-flow and network summary for a session — useful for understanding what a replay exercises. |

### Triggering a test run

| Tool | Access | What it does |
|---|---|---|
| `request_asset_upload` | Write | Request a signed URL to upload a zipped static-asset build. |
| `register_asset_build` | Write | Register an uploaded zipped asset build as a reusable deployment. |
| `request_container_upload` | Write | Request registry credentials to push a Docker container build. |
| `register_container_build` | Write | Register a pushed container build as a reusable deployment. |
| `trigger_test_run` | Write | Trigger a test run against a registered deployment (or one already uploaded for a commit). |
| `complete_base_run` | Write | Replay the selected sessions a base run has not run yet. |

### Feedback

| Tool | Access | What it does |
|---|---|---|
| `submit_feedback` | Write | Send free-form feedback about Meticulous to the Meticulous team. |

---

## What this connector can access

- **Reads:** your identity and the projects you can access; test runs and their status; screenshot diffs, outcomes, and images; DOM and timeline diffs; JavaScript coverage; and recorded session data (the user flow and network requests a session exercises).
- **Writes** (only when the corresponding tool is called): uploads a build's assets or container image, registers it as a deployment, triggers a new test run against it, changes your account's default project, and submits feedback to the Meticulous team.
- **Never accesses:** your repository's source code, or PR titles/descriptions — Meticulous's diffs and coverage are computed from screenshots, DOM snapshots, and instrumented JS execution, not from reading your code.
- **Scope:** every call is scoped to the projects the authenticated user (or, for a project API token, the single owning project) has access to.

---

## Troubleshooting

- **401/403 on every call:** your token has expired or was revoked — reconnect via `/mcp` (Claude Code) or your client's equivalent to re-authenticate.
- **"No default project" errors:** call `set_project` (`list_projects` shows the options), set one from your [user settings](/user-settings) in the web app, or run `meticulous auth set-project` — or, for a one-off call, pass the optional `project` argument most tools accept (id, `org/proj`, or simply `proj`; only honored for an OAuth user token, not a project or test-run API token).
- **A lookup returns nothing when you expected results:** it may have run against a different project. The default project is stored against your user account, so it is shared with the CLI and every other session, and changing it anywhere changes it everywhere. Call `get_project` to see which project is in play — an empty test-run or session result already names it for you.
- **SSO-only organizations:** if your organization enforces a specific identity provider, a token issued outside that flow is rejected; log in again through your organization's SSO entry point.
- Anything else: contact us (see below).

---

## Support and policies

- **Support:** [support@meticulous.ai](mailto:support@meticulous.ai)
- **Privacy policy:** [meticulous.ai/privacy-policy](https://www.meticulous.ai/privacy-policy)
- **Terms of service:** [meticulous.ai/terms-conditions](https://www.meticulous.ai/terms-conditions)
- **Security and compliance:** [security.meticulous.ai](https://security.meticulous.ai/)
