Skip to main content
Glama
meovan07

mcp-evidence-api

by meovan07
README.md
# mcp-evidence-api

An MCP server that makes backend/API requests and packages request+response
pairs as **evidence** — proving an endpoint works. Sibling project to
[mcp-evidence](https://github.com/meovan07/mcp-evidence), which does the same
thing for browser/UI flows; this one is for testing backend behavior with no
browser involved (video/trace don't mean anything without a page, so this is
a separate, much simpler tool rather than an extension of that one).

Generic and reusable: no assumptions about any particular API. You pass a
`baseUrl` per session, and evidence is written into the *consuming* project's
working directory at `.evidence/<featureName>/<timestamp>/`.

## Install

### Register with Claude Code

Per-user (available in every project):

```bash
claude mcp add --scope user evidence-api -- npx -y github:meovan07/mcp-evidence-api
```

Or per-project, add to `.mcp.json`:

```json
{
  "mcpServers": {
    "evidence-api": {
      "command": "npx",
      "args": ["-y", "github:meovan07/mcp-evidence-api"]
    }
  }
}
```

This repo is public, so no GitHub credentials are needed on the machine
running `npx`.

Consuming projects should add `.evidence/` to their own `.gitignore`.

## Tools

| Tool | Purpose |
|---|---|
| `start_evidence_session({ featureName, baseUrl? })` | Creates the evidence dir. Returns `sessionId`. |
| `request({ sessionId, name?, method, url, headers?, body? })` | Makes an HTTP request, logs the full request/response pair. `url` resolves against `baseUrl` if relative. |
| `query({ sessionId, name?, sql, params? })` | Runs a read-only SQL query against Postgres and logs it — see below. |
| `finish_evidence_session({ sessionId, summary? })` | Writes `requests.json`, `queries.json`, and `manifest.json`, returns request/failure/query counts. |
| `wait_for_email({ from?, subjectContains?, pattern?, timeoutMs?, pollIntervalMs? })` | Polls an IMAP inbox for a new email (e.g. an OTP/verification code) and returns it — see below. Independent of the evidence-session tools above; doesn't write anything to `.evidence/`. |

A session left open for 10 minutes with no tool calls is auto-finished. The
server also flushes any open sessions on `SIGINT`/`SIGTERM` so evidence isn't
lost if the process is killed mid-run.

Sensitive request/response **headers** (`authorization`, `cookie`,
`set-cookie`, `x-api-key`, `api-key`, `x-auth-token`, `proxy-authorization`)
are redacted before being written to disk. This only covers header names —
if an endpoint echoes a secret back inside a JSON response *body*, that isn't
redacted, since there's no reliable way to tell a secret-looking field from a
normal one. Don't point this at endpoints that echo credentials in response
bodies without being aware evidence files will contain them in plaintext.

### Example

```
start_evidence_session({ featureName: "users api", baseUrl: "http://localhost:4000" })
  -> sessionId, evidenceDir

request({ sessionId, name: "create user", method: "POST", url: "/users", body: { name: "Ada" } })
request({ sessionId, name: "get user", method: "GET", url: "/users/1" })
finish_evidence_session({ sessionId, summary: "Users API create+fetch works" })
```

Resulting evidence directory:

```
.evidence/users-api/2026-07-08T08-41-21-754Z/
  requests.json
  manifest.json
```

`requests.json` is an array of full request/response records (method, url,
headers, body, status, timing). `manifest.json` is a summary: request count
and how many came back non-2xx.

### `query` (database verification)

An API response can claim an action succeeded without the database actually
reflecting it correctly (soft-deletes, audit fields, related-table writes).
`query` lets you check the real state directly — currently Postgres only.

**Setup**: set `DATABASE_URL` as an environment variable on the MCP server
registration, same pattern as the IMAP credentials — run this yourself,
don't have an agent run it or inspect it afterward:

```bash
claude mcp add --scope user evidence-api \
  -e DATABASE_URL=postgres://user:password@host:5432/dbname \
  -- npx -y github:meovan07/mcp-evidence-api
```

**Read-only, enforced twice**: input is rejected unless it's a single
`SELECT`/`WITH ... SELECT` statement (no semicolon-separated multi-statement
tricks), *and* every query runs inside a database-level read-only
transaction (`SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY`), so a
write hidden inside something like a CTE — which could slip past the input
check — still gets rejected by Postgres itself. Validated directly: a
`WITH deleted AS (DELETE ... RETURNING *) SELECT * FROM deleted` attempt was
rejected with `cannot execute SELECT in a read-only transaction`, and the row
was confirmed still present afterward.

```
query({ sessionId, name: "confirm signup row", sql: "SELECT * FROM users WHERE email = $1", params: ["ada@example.com"] })
  -> "1 row(s) (2ms)\n[{\"id\":1,\"email\":\"ada@example.com\",...}]"
```

Results over 50 rows are truncated in the evidence file (the tool's response
still reports the true `rowCount`); string cell values over 2000 characters
are truncated too.

### `wait_for_email` (OTP / verification-code retrieval)

For test flows that require an email OTP (signup, login, password reset).
Reads over IMAP — no browser, no Gmail login automation, nothing that breaks
when a mail provider's web UI changes.

**Setup**: requires an IMAP-accessible mailbox and an app password (Gmail:
enable 2-Step Verification, then generate one at
[myaccount.google.com/apppasswords](https://myaccount.google.com/apppasswords)).
Set these as environment variables on the MCP server registration — never
commit them, never put them in a tracked file:

```bash
claude mcp add --scope user evidence-api \
  -e IMAP_USER=you@gmail.com \
  -e IMAP_APP_PASSWORD=xxxxxxxxxxxxxxxx \
  -- npx -y github:meovan07/mcp-evidence-api
```

Run this yourself in your own terminal rather than having an agent run it —
`claude mcp get <name>` echoes registered env vars back in plaintext, so
credentials shouldn't pass through a session that might inspect it later.

(`IMAP_HOST` is optional, defaults to `imap.gmail.com`.)

**Behavior**: only matches emails that arrive *after* the tool call starts
(with a small clock-skew buffer), so it never accidentally picks up a stale
OTP from an earlier run. Polls until a match or `timeoutMs` (default 30s)
elapses, then throws a clear timeout error.

```
wait_for_email({ subjectContains: "verification code", pattern: "\\d{6}" })
  -> "Matched: 482913 (from email \"Your verification code\" sent by noreply@yourapp.com)"
```

If you don't pass `pattern`, it returns the full email (from/subject/date/body
text) instead of trying to extract a code — useful for reading a magic link
URL, for example.

**Security notes**: the app password should be scoped to nothing but this —
generate one just for this purpose, and revoke it if you ever suspect it
leaked. This tool only reads mail; it can't send, delete, or modify anything.
The extracted code is returned as plain text in the tool result (by design —
that's how you use it), so don't point this at a mailbox that receives
anything more sensitive than test/verification emails.

## Development

```bash
npm install
npm run build   # tsc -> dist/
npm run dev     # tsc --watch
npm start        # node dist/index.js
```

TDQS

A4/5.0

Scored across 3 tools

Disambiguation5/5

Each tool has a distinct role: start session, make request, finish session. There is no overlap in functionality.

Naming Consistency4/5

Tools use snake_case with imperative verbs. 'request' is less descriptive but still follows the verb pattern; the other two follow verb_noun. Overall consistent.

Tool Count5/5

Three tools perfectly cover the session lifecycle: start, execute, finish. No redundancy or missing steps.

Completeness5/5

The toolset provides a complete workflow for recording HTTP evidence: session creation, request logging, and session finalization. No obvious gaps.

Maintenance

ActivityStale
ResponsivenessNo issues