MCP server
MetaMonster runs a remote MCP server at:
https://new.metamonster.ai/mcp
It's a stateless Streamable HTTP endpoint — no session state, so any request can land on any server instance. Authentication is the same mm_ API key as the REST API, sent as a bearer token:
Authorization: Bearer mm_YOUR_API_KEY
There's no separate MCP-specific key or scope model — an MCP call spends the same rate-limit budget and respects the same scopes as the REST call behind it (see Authentication).
The server wraps 15 curated tools over the v1 API — not a 1:1 endpoint mirror. Each tool is a small workflow step (find pages, stage an edit, resolve a recommendation, run an analysis) so a chat agent can drive the whole verification loop without knowing the REST shapes underneath.
claude.ai web and Claude Desktop connect via OAuth, through the custom-connector flow below — sign in, pick the organization to authorize, and approve. Claude Code, Cursor, and Windsurf connect directly with a bearer API key since they support header-based auth on a remote server.
Setup
Claude Code
claude mcp add --transport http metamonster https://new.metamonster.ai/mcp --header "Authorization: Bearer mm_YOUR_KEY"
Cursor / Windsurf
Add to mcp.json:
{
"mcpServers": {
"metamonster": {
"url": "https://new.metamonster.ai/mcp",
"headers": { "Authorization": "Bearer mm_YOUR_KEY" }
}
}
}
claude.ai web / Claude Desktop (OAuth connector)
Both connect the same way, through the custom-connector UI:
- Go to Settings → Connectors → Add custom connector.
- Enter the server URL:
https://new.metamonster.ai/mcp. - Sign in with your MetaMonster account.
- Pick the organization to authorize (if you belong to more than one).
- Approve.
No API key to copy or paste — the connector flow issues and refreshes its own token. Revoke access anytime from Settings → Connected apps in the app; revoking cuts off MetaMonster API access immediately.
To switch which organization the connector is authorized for, revoke it in Settings → Connected apps, then reconnect and pick the new organization — there's no in-place org switch.
Tools
All 15 tools return a short human-readable summary plus a structured JSON payload mirroring the REST data shape (same snake_case field names as the REST API).
| Tool | What it does | REST endpoint(s) |
|---|---|---|
list_sites |
List the sites in your organization. Start here unless you already have a page URL. | GET /sites |
find_pages |
Search/filter a site's pages by URL, priority, grade, score, or recommendation presence. | GET /sites/{siteId}/pages |
get_opportunities |
Ranked "what should I work on" for a site — homepage + scored opportunity rows + cross-page recommendations. Requires an active subscription. | GET /sites/{siteId}/opportunities |
get_rubric |
What the site's grader measures — components, weights, criteria. Read once per site before editing. | GET /sites/{siteId}/rubric |
run_checks |
Free, instant, deterministic hygiene checks (title/meta length, keyword placement, JSON-LD validity, link counts). Not the grade. | GET /pages/{pageId}/checks |
get_page_report |
Full report for a page without triggering an analysis: fields, latest analysis, recommendations, keywords, GSC queries, SERP, comparison vs. the previous run. Accepts page_id or url. |
GET /pages/lookup (if url) → GET /pages/{pageId}/report |
update_page |
Set a page's keywords, priority flag, or purpose. Stores exactly what you send; no AI. | PATCH /pages/{pageId} |
set_field_draft |
Stage a new title, meta description, h1, primary keyword, body, image alt, or schema (JSON-LD) for a page. Empty value discards the draft. | PUT/DELETE /pages/{pageId}/drafts/{field} |
update_content |
Replace the page body with markdown (creates a content version). | POST /pages/{pageId}/content |
resolve_recommendation |
"I made this change" — validates current content/draft against the recommendation and marks it applied on a pass. | POST /recommendations/{id}/resolve |
dismiss_recommendation |
"Not doing this" — marks a recommendation dismissed with an optional rationale. | POST /recommendations/{id}/dismiss |
recheck_recommendation |
Re-verify an already-applied recommendation after further edits (clears placeholders, confirms a skipped verdict). | POST /recommendations/{id}/recheck |
get_job |
Status of an async job (job_an_… analysis, job_cr_… crawl). Use when a tool returned status: "processing". |
GET /jobs/{jobId} |
recrawl_page |
Re-fetch the live page after edits made directly on the site (not through these tools). | POST /pages/{pageId}/recrawl |
analyze_page |
Run a full analysis and return the report. Accepts page_id or url; resolves or creates the page (and, with create_site: true, the site) as needed. Spends one unit of the org's plan allowance. |
GET /pages/lookup, POST /sites, POST /sites/{siteId}/pages, POST /pages/{pageId}/analyze, GET /pages/{pageId}/report |
Analyze a URL
analyze_page and get_page_report both accept { url } instead of { page_id }, so a client can hand over a bare URL and get a report back without knowing whether MetaMonster has seen the page before. analyze_page resolves it through three cases:
- Known page — the URL already resolves to a page. If it has no crawled snapshot yet (e.g. a page created but never fetched), it's crawled first; otherwise it's analyzed (or its existing report is returned) directly. A page found
planned(no live URL yet) returnspage_not_liveinstead of guessing a URL to crawl. - Known site, unknown page — the URL's host matches an existing site but the path doesn't. The page is created and crawled first, then analyzed.
- Unknown site — neither the host nor the path is known. The tool returns
site_not_foundwith anext_stephint instead of guessing. Confirm with the user, then callanalyze_pageagain withcreate_site: trueto add the site (this queues a background discovery crawl of the whole site) — only the one page you asked about is analyzed; site creation never spends extra allowance on its own.
A mistyped URL on a known site creates a page that can't be deleted through MCP (there's no delete tool yet), so confirm uncertain paths with the user before calling analyze_page with a URL.
Only analyze_page will create a site or page on your behalf, and only with explicit opt-in (create_site: true). get_page_report only reads what already exists (page_not_found/site_not_found otherwise), and recrawl_page only accepts page_id — it doesn't resolve a URL.
Errors
Tool errors carry the same code/message/details as the REST error envelope — payment_required, analysis_in_progress, crawl_in_progress, site_not_found, page_not_found, already_resolved, and so on. A couple of MCP-layer additions:
invalid_arguments— the tool call itself was malformed (bad zod input); fix the arguments and retry.page_not_live—analyze_pagewas asked to analyze aplannedpage (no live URL yet) with no crawled or saved content; add content withupdate_contentor take the page live and recrawl instead.analyze_page's crawl and analysis waits share a single ~75-second server-side budget for the whole call (not ~90s per step) — the second wait gets whatever's left after the first. If the budget runs out while a step is still running, the tool returnsstatus: "processing"with ajob_idinstead of an error — callget_jobto poll it, honoring itsretry_after_seconds.
Rate limits are the calling key's normal REST limits (see Conventions) — there's no separate MCP-layer limit; each tool call spends the budget of the v1 request(s) it makes underneath.
Local development
Run against a local dev server at http://localhost:3000/mcp with the same bearer auth. The MCP Inspector is the fastest way to poke at it directly:
npx @modelcontextprotocol/inspector
In the Inspector UI, choose transport Streamable HTTP, set the URL to http://localhost:3000/mcp, and add an Authorization: Bearer mm_YOUR_KEY header.