网页截图与视觉差异监控MCP服务

Captureze MCP 服务让AI代理能够对任意网页进行定时截图、查看视觉差异并获取捕获证书,适用于网站监控、变更审计与证据留存等场景。

  • by Captureze
  • TypeScript
  • 本地部署
  • 更新于
在 GitHub 查看 Captureze/mcp

使用说明

Captureze MCP server

Model Context Protocol server for Captureze — scheduled website screenshots, visual diffs and Certificates of Capture, exposed as tools an AI agent can call.

With it connected, an agent can:

  • screenshot any URL and look at the image (proxy-backed, so bot-protected and geo-restricted pages work);
  • keep a page under watch on a cron and report what changed and by how much;
  • pull a Certificate of Capture (SHA-256, timestamp, capture origin, verification URL) for evidence work;
  • detect a site's cookie consent banner and its accept/reject selectors.

Works with any MCP client: Claude Code, Claude Desktop, claude.ai, ChatGPT (developer mode / deep research connectors), OpenClaw, and anything built on the MCP SDKs or an agent framework with MCP support.

Requirements

  • A Captureze API key (cap_...) — Captureze console → Settings → API keys
  • Node.js 20+ — only if you run the server yourself (stdio, or a self-hosted HTTP deployment)

Quick start

Remote clients (claude.ai, ChatGPT, hosted agents) — point them at the endpoint we run:

https://mcp.captureze.com/mcp

Connector dialogs — ChatGPT's and claude.ai's — sign in over OAuth against your Captureze account, so the URL is all you type. Clients that authenticate by header send Authorization: Bearer cap_... instead. Either way there is nothing to install and nothing to deploy.

Local clients (Claude Code, Claude Desktop, OpenClaw) — run it over stdio:

CAPTUREZE_API_KEY=cap_xxx npx -y @captureze/mcp

You can also run the HTTP transport yourself; see Self-hosting the HTTP transport.

Connect it

Claude Code

claude mcp add captureze --env CAPTUREZE_API_KEY=cap_xxx -- npx -y @captureze/mcp

Claude Desktop / any stdio client

{
  "mcpServers": {
    "captureze": {
      "command": "npx",
      "args": ["-y", "@captureze/mcp"],
      "env": { "CAPTUREZE_API_KEY": "cap_xxx" }
    }
  }
}

Claude Code (remote)

claude mcp add -s user --transport http captureze https://mcp.captureze.com/mcp \
  --header "Authorization: Bearer cap_xxx"

ChatGPT (custom connector, developer mode)

ChatGPT only talks to remote servers. Turn on developer mode, add a connector pointing at https://mcp.captureze.com/mcp, pick OAuth and press connect: you sign in with the same Captureze account you use on the site, and there is no key to paste. The endpoint answers the discovery documents on every path ChatGPT probes, so the dialog finds the authorization server by itself — the URL is the only thing you type.

An API key still works if you would rather use one (Access token / API key → Authorization: Bearer cap_...). Either way the server ships the search and fetch tools deep research requires alongside the captureze_* ones. See docs/INTEGRATIONS.md.

claude.ai (custom connector)

Add https://mcp.captureze.com/mcp as a custom connector and press connect: the endpoint speaks OAuth 2.1, so you sign in with the same Captureze account you use on the site and there is no key to paste. Clients that can attach an Authorization header may still use an API key instead — both work, on the same endpoint. See docs/INTEGRATIONS.md.

OpenClaw

{
  "mcpServers": {
    "captureze": {
      "command": "npx",
      "args": ["-y", "@captureze/mcp"],
      "env": { "CAPTUREZE_API_KEY": "cap_xxx" }
    }
  }
}

Hermes-based and other OpenAI-compatible agent loops connect through their MCP client layer — stdio locally, https://your-host/mcp remotely. Details for each in docs/INTEGRATIONS.md.

Tools

Tool What it does
captureze_capture_url Screenshot a URL now; returns the image and the change vs the previous capture
captureze_capture_site Capture an existing site with its stored settings
captureze_list_sites / captureze_get_site What the account monitors
captureze_monitor_site Start capturing a page on a cron
captureze_update_site / captureze_delete_site Change or remove a site (delete is destructive)
captureze_list_captures Capture history with per-capture change percentages
captureze_get_capture_image Look at a stored capture, or its diff overlay
captureze_list_capture_runs Execution log — why a scheduled capture failed
captureze_compare_captures Pixel-diff any two captures (Pro)
captureze_diff_trend How much a page has been moving (Pro)
captureze_get_capture_certificate Certificate of Capture for evidence, with its RFC 3161 timestamp (Starter+)
captureze_detect_consent_banner / captureze_get_consent_detection Find the cookie banner and its selectors
captureze_account_status Plan, entitlements, usage against limits
search / fetch ChatGPT-compatible views over the same data

Resources: captureze://sites, captureze://viewport-presets. Prompts: watch-page, evidence-pack, consent-audit.

How ad-hoc captures are stored

Every capture belongs to a site, which is what gives it history, diffs and certificates. So captureze_capture_url files its capture under a site for that URL: an existing one if the account already has it, otherwise a new one created paused (is_active: false), which captures on request and never on its own. Pass monitor: true to have it run on a schedule too. That also applies when the site already exists: a paused site for the URL is resumed and moved to cron_expression, and the result says what changed. captureze_monitor_site reuses an existing site for the URL the same way, keeping its capture history, instead of creating a second one. A capture without monitor never pauses, reschedules or reconfigures an existing site.

Creating or resuming a monitor takes one capture straight away, as the baseline later captures are diffed against. It uses one capture credit and comes back as initial_capture; it is null when the call only changed the schedule of a site already being monitored.

What a response contains

Every tool answers with the same allow-listed objects: a site, a capture (with its stage: single, or pre_consent / post_accept / post_reject for a consent-flow capture), a run, the account. A site says whether it sends credentials, extra headers or a script (has_auth, has_custom_headers, has_inject_script) but never what they are.

Timeouts and retries

A capture takes 10-60 seconds and is billed. The server starts it in async mode and polls it, so no request is held open for the whole capture, and every capture carries an idempotency key. Pass your own as idempotency_key to captureze_capture_url, captureze_capture_site or captureze_monitor_site. If the call times out, the capture still finishes on the server: call again with the same key and you get that capture back, without a second charge. Reusing a key with different options is refused. When the MCP server itself runs out of time, its error names the key to retry with. Only one on-demand capture of a site runs at a time. A call made while one is running waits for that one and says so, instead of starting a second.

CLI

The same tools are reachable by hand, which is the quickest way to tell "the agent asked for the wrong thing" apart from "the API misbehaved":

export CAPTUREZE_API_KEY=cap_xxx        # or put it in a .env file

captureze-mcp sites                     # what the account monitors
captureze-mcp capture https://example.com --full-page --out shot.png
captureze-mcp captures <site-id>        # history with change percentages
captureze-mcp compare <capture-id> <capture-id>
captureze-mcp account                   # plan, entitlements, usage
captureze-mcp --help                    # every command

Output is JSON on stdout. With no command the same binary starts the MCP server, so existing client configs are unaffected.

Configuration

Variable Default Meaning
CAPTUREZE_API_KEY — API key. Required for stdio; the fallback for HTTP requests without an Authorization header
CAPTUREZE_BASE_URL https://captureze.com Captureze install (self-hosted or staging)
CAPTUREZE_TIMEOUT_MS 180000 Upstream timeout — captures run a real browser and are slow
CAPTUREZE_MAX_INLINE_IMAGE_BYTES 4500000 Above this, tools return a URL instead of inlining the image
PORT / HOST 8787 / 127.0.0.1 HTTP transport bind
CAPTUREZE_MCP_ALLOWED_HOSTS — Host header allowlist when binding beyond localhost

A .env file in the working directory is loaded on startup.

OAuth (HTTP transport, optional)

Set these to let clients that can only authenticate by OAuth — the claude.ai connector dialog among them — sign in against a Clerk instance. Leave them unset and none of it loads: the endpoint stays exactly what it is on an API key, which is what a self-hosted install wants.

Variable Meaning
CLERK_PUBLISHABLE_KEY pk_live_… / pk_test_…. Names the authorization server
CLERK_SECRET_KEY sk_live_… / sk_test_…. Verifies access tokens
CAPTUREZE_MCP_PUBLIC_URL This endpoint's public origin. Defaults to what the request says it is

With them set the server publishes RFC 9728 protected resource metadata and mirrors the issuer's RFC 8414 authorization server metadata, and a 401 carries the resource_metadata pointer that lets a connector find them. API keys keep working unchanged.

Connectors disagree about where those documents live, so each is served everywhere one is looked for. The spec derives the first of each pair from a resource with a path; the rest are what a client asks for when it has no pointer to follow, which is how ChatGPT's connector looks for an authorization server:

Path Document
/.well-known/oauth-protected-resource/mcp RFC 9728 — this endpoint, and who issues for it
/.well-known/oauth-protected-resource the same document, for clients that probe here
/.well-known/oauth-authorization-server RFC 8414, mirrored from the issuer
/.well-known/oauth-authorization-server/mcp the same mirror
/.well-known/openid-configuration the same mirror
/.well-known/openid-configuration/mcp the same mirror

The OpenID paths answer with the OAuth document deliberately: it is the one that carries registration_endpoint, and a client that asks there is an MCP client following its discovery chain, not an OpenID relying party.

An unauthenticated GET /mcp is answered with the same 401 and pointer rather than 405, so a connector that probes the endpoint before it knows how to authenticate is not sent to a dead end.

Self-hosting the HTTP transport

You do not need this to use Captureze from a remote client — https://mcp.captureze.com/mcp is already running. Self-host when you point at a self-hosted Captureze install, or when you want the endpoint inside your own network.

# published image
docker run --rm -p 8787:8787 -e HOST=0.0.0.0 \
  -e CAPTUREZE_MCP_ALLOWED_HOSTS=mcp.example.com \
  ghcr.io/captureze/mcp:latest --http

# or build it from this repo
docker build -t captureze-mcp .
docker run --rm -p 8787:8787 -e HOST=0.0.0.0 \
  -e CAPTUREZE_MCP_ALLOWED_HOSTS=mcp.example.com captureze-mcp --http

Images are published to ghcr.io/captureze/mcp on every push to main (:latest and an immutable :main-<short-sha>) and on every v* release tag (:0.1.0). Pin an immutable tag in production — never :latest.

The HTTP transport is stateless and multi-tenant: each request carries its own credential — an API key, or an OAuth access token — a fresh server instance handles it, and nothing about one caller survives into the next request. Any instance can answer any request, so it scales behind an ordinary load balancer with no sticky sessions.

Terminate TLS in front of it, keep CAPTUREZE_MCP_ALLOWED_HOSTS set to the public hostname, and do not set CAPTUREZE_API_KEY on a shared deployment — that key would become the fallback for every unauthenticated request.

Development

npm install
npm test            # node:test, no network
npm run lint
npm run format      # prettier --write
npm run typecheck
npm run build

Releasing

The package is published to npm as @captureze/mcp — that is what makes npx -y @captureze/mcp work. Tagging is the trigger:

npm version patch          # or minor / major
git push --follow-tags

The release workflow runs lint, format, typecheck, tests and build, checks the tag matches package.json, and publishes with provenance. It needs an NPM_TOKEN repository secret (an npm automation token), unless the repo is set up as a trusted publisher on npm.

License

MIT