MCP server
MetaMonster runs a remote MCP server at:
https://new.metamonster.ai/mcp
It's a stateless Streamable HTTP endpoint. There's 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 43 curated tools over the v1 API (39 for organizations on the new analysis), not a 1:1 endpoint mirror. Each tool is a small workflow step (find pages, stage an edit, resolve a recommendation, run an analysis, generate a brief or a fix) so a chat agent can drive the whole verification loop, and everything the app can generate, 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.
There's 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
Every tool returns a short human-readable summary plus a structured JSON payload mirroring the REST data shape (same snake_case field names as the REST API). Organizations on the new analysis get 46 tools: the five checks_* tools are dropped and write_recommendations is added. Everyone else gets 50.
Nothing a tool generates is published to your CMS. Drafts, content versions and alt text are saved in MetaMonster for review.
Find and read
| Tool | What it does | REST endpoint(s) |
|---|---|---|
list_sites |
Lists the sites in your organization. Start here unless you already have a page URL. | GET /sites |
find_pages |
Searches and filters a site's pages by URL, priority, grade, score, or recommendation presence (organizations on the new analysis filter by min_factors_passing_pct instead of grade). |
GET /sites/{siteId}/pages |
get_opportunities |
Returns the ranked "what should I work on" report for a site: the homepage, scored opportunity rows, and cross-page recommendations. Requires an active subscription. | GET /sites/{siteId}/opportunities |
get_rubric |
Returns what the site's grader measures: components, weights, and criteria (the factor list on the new analysis). Read it once per site before editing. | GET /sites/{siteId}/rubric |
run_checks |
Runs free, instant, deterministic hygiene checks (keyword placement, JSON-LD validity, link counts). These are not the grade. | GET /pages/{pageId}/checks |
get_page_report |
Returns the full report for a page without triggering an analysis: fields, latest analysis, recommendations, keywords, GSC queries, SERP, and a comparison against the previous run. Accepts page_id or url. |
GET /pages/lookup (if url) → GET /pages/{pageId}/report |
get_analysis_history |
Lists a page's past analyses (score and grade, or factors passing on the new analysis). | GET /pages/{pageId}/analyses |
get_job |
Returns the status of an async job: job_an_... / job_ck_... (analysis), job_cr_... (crawl), job_ol_... (content brief), job_ac_... (AI fix), job_rw_... (body draft). Use it when a tool returned status: "processing". |
GET /jobs/{jobId} |
keyword_research |
Returns search volume, difficulty, CPC and intent for a seed keyword, plus related keyword ideas with the same metrics (DataForSEO). Pass site_id to use the site's search market. Each live lookup counts against the organization's daily limit (500, shared with the app); repeating a keyword within 30 days is served from cache for free. |
GET /keywords/research |
Edit and verify
| Tool | What it does | REST endpoint(s) |
|---|---|---|
update_page |
Sets a page's keywords, priority flag, or purpose. Stores exactly what you send, with no AI involved. | PATCH /pages/{pageId} |
set_field_draft |
Stages a new title, meta description, h1, primary keyword, body, or schema (JSON-LD) for a page. An empty value discards the draft. Alt text is per image: use set_image_alt. |
PUT/DELETE /pages/{pageId}/drafts/{field} |
update_content |
Replaces the page body with markdown, creating a content version. | POST /pages/{pageId}/content |
list_content_versions |
Lists a page's body versions (source, word count, date). | GET /pages/{pageId}/content-versions |
restore_content_version |
Restores an older body version by copying it forward as a new version. No version is deleted. | POST /pages/{pageId}/content-versions/{versionId}/restore |
resolve_recommendation |
Tells MetaMonster "I made this change": validates the current content or draft against the recommendation and marks it applied on a pass. | POST /recommendations/{id}/resolve |
dismiss_recommendation |
Tells MetaMonster "not doing this": marks a recommendation dismissed, with an optional rationale. | POST /recommendations/{id}/dismiss |
recheck_recommendation |
Re-verifies an already-applied recommendation after further edits (clears placeholders, confirms a skipped verdict). | POST /recommendations/{id}/recheck |
recrawl_page |
Re-fetches the live page after edits made directly on the site rather than through these tools. | POST /pages/{pageId}/recrawl |
analyze_page |
Runs a full analysis and returns the report. Accepts page_id or url, and 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 |
write_recommendations |
Organizations on the new analysis only. Writes (or rewrites) the recommendations for the page's latest analysis from its stored factor results, without re-running the analysis. Free. Returns at once; the writing takes about 20 seconds, then read them with get_page_report. Use it when an analysis reports recommendations_status: "not_requested". |
POST /pages/{pageId}/checks/recommendations |
create_page |
Adds a page for content that isn't published yet, like the app's "Add page". Nothing is crawled. Write it with update_content and set_field_draft, then analyze_page reviews the saved content. On a planned site, the page joins the plan with no URL until go_live_site. |
POST /sites/{siteId}/pages (crawl: false) |
delete_page |
Deletes a page created by mistake, such as a mistyped URL. A soft delete: the page leaves every listing, and the live website is not touched. | DELETE /pages/{pageId} |
Plan a new site
For a site that isn't live yet, the same flow as the app's "Plan a site".
| Tool | What it does | REST endpoint(s) |
|---|---|---|
plan_site |
Creates a planned site from a brief about the business (no domain, no crawl) and proposes the pages it needs, each with a name, slug and purpose. Takes 10-40 seconds. | POST /sites/plan, POST /sites/{siteId}/plan/generate, GET /sites/{siteId}/plan |
get_site_plan |
Lists the planned pages with their paths, purposes and primary keywords, and whether keyword assignment is still running. | GET /sites/{siteId}/plan |
edit_site_plan |
Removes, edits and adds planned pages in one call, or regenerates the whole set from the brief (optionally a revised one). | DELETE/PATCH /sites/{siteId}/plan/pages/{pageId}, POST /sites/{siteId}/plan/pages, POST /sites/{siteId}/plan/generate |
assign_planned_keywords |
Assigns a primary keyword to every planned page that has none, then waits up to ~45 seconds for it to finish. Spends credits per page, as in the app. | POST /sites/{siteId}/plan/keywords, GET /sites/{siteId}/plan |
go_live_site |
Sets the site's real domain and starts a crawl. Crawled URLs are matched to the planned pages by path, so their keywords, briefs and content carry over. | POST /sites/{siteId}/go-live |
Generate
| Tool | What it does | REST endpoint(s) |
|---|---|---|
get_content_brief |
Reads the page's latest content brief: its angle and its proposed sections with their status. | GET /pages/{pageId}/outline |
generate_content_brief |
Generates a content brief from the page's primary keyword and SERP, then waits for it (about 45 seconds at most). Counts as a page against the plan allowance. | POST /pages/{pageId}/outline → GET /jobs/{jobId} |
update_content_brief |
Accepts, rejects, revises or reorders brief sections. Accepted headings go into the body as a new content version; write the sections next with update_content, passing the returned content_version_id as base_version_id. |
PATCH /pages/{pageId}/outline/sections |
generate_fix |
Generates an AI fix for one recommendation (or for a title, meta description, h1 or schema with your instructions) and returns the before/after. Nothing is applied yet. Capped at 15 generations per page per analysis, revisions included (generation_cap_reached). |
POST /recommendations/{recId}/fix or POST /pages/{pageId}/actions → GET /jobs/{jobId} → GET /actions/{actionId} |
review_fix |
Accepts, rejects or revises a generated fix. Accepting saves a metadata fix as the field's draft, or a body fix as a new content version. | POST /actions/{actionId}/accept, /reject, /revise |
generate_content_draft |
Rewrites the body with several body recommendations at once (the app's Review Mode). The result is saved as the latest content version straight away, with no review step. Costs 1 credit. | POST /pages/{pageId}/content-rewrites → GET /jobs/{jobId} → GET /pages/{pageId}/content-versions/{versionId} |
generate_field_draft |
Generates a title, meta description or h1 draft, with optional instructions. Costs credits, like the app. | POST /pages/{pageId}/drafts/{field}/generate |
get_page_images |
Lists the page's images with their alt text state (missing, decorative, draft or crawled). | GET /pages/{pageId}/images |
set_image_alt |
Sets one image's alt text, or generates it with generate: true. |
PUT /pages/{pageId}/images/alt, POST /pages/{pageId}/images/generate-alt |
generate_missing_alt |
Generates alt text for every image still missing it, within a ~45-second budget per call. Call again to continue. Costs credits per image. | GET /pages/{pageId}/images, POST /pages/{pageId}/images/generate-alt |
Business facts and the Context Library
| Tool | What it does | REST endpoint(s) |
|---|---|---|
get_site_facts |
Reads the site's verified business facts (company details, contact info, Google listing, testimonials, stats, awards, clients, people) with their citations. Search the full library with search; status: "dismissed" lists removed facts and what removed each. |
GET /sites/{siteId}/facts |
add_site_facts |
Records facts the user stated in conversation so later generations use them. Deduped by content; never overwrites a fact a person edited. | POST /sites/{siteId}/facts |
update_site_fact |
Edits a fact's value, confirms a stale fact is still true, deletes a fact (a fact read from the site is dismissed instead), or restores a removed one (undoing the dismissal that removed it, when there was one). | PATCH/DELETE /sites/{siteId}/facts/{factId}, POST /sites/{siteId}/facts/{factId}/restore |
get_site_guidance |
Reads what every generation is told: brand voice, about the business, audiences per offer, preferences and voice notes, and fact-scan status. | GET /sites/{siteId}/guidance |
update_site_guidance |
Edits brand voice and business context, and adds, edits or archives preferences and voice notes. | PATCH /sites/{siteId}, POST /sites/{siteId}/guidance, PATCH /sites/{siteId}/guidance/{guidanceId} |
manage_audiences |
Adds, edits or removes who an offer is for, or regenerates the generated rows. | POST/PATCH/DELETE /sites/{siteId}/audiences..., POST /sites/{siteId}/audiences/regenerate |
scan_site |
Rescans the site for facts (optionally with new skip patterns or instructions), or refreshes only the Google listing. Runs in the background. | POST /sites/{siteId}/facts/extract, GET/PUT /sites/{siteId}/facts/settings, POST /sites/{siteId}/facts/google-listing |
list_dismissal_fact_changes |
Lists the fact changes made from the reasons people gave when dismissing recommendations. | GET /sites/{siteId}/dismissal-routing/facts |
undo_dismissal_fact_change |
Reverses the Context Library change a dismissal reason made. | POST /recommendations/{recId}/dismissal-routing/undo |
Checks engine (preview)
Organizations that are not on the new analysis also get these five tools. They run the checks engine as a separate lineage: no score or grade, and not shown in the app.
| Tool | What it does | REST endpoint(s) |
|---|---|---|
checks_analyze_page |
Runs a checks analysis and returns its factor verdicts with per-check receipts. | POST /pages/{pageId}/checks/analyze → GET /pages/{pageId}/checks/analysis |
checks_get_report |
Reads the latest checks analysis without running one. | GET /pages/{pageId}/checks/analysis |
checks_run |
Runs the deterministic checks against the page's current state. Free; nothing is stored. | GET /pages/{pageId}/checks/preview |
checks_regenerate_recommendations |
Queues a rewrite of a checks analysis's recommendations from its stored results. | POST /pages/{pageId}/checks/recommendations |
checks_get_rubric |
Lists every factor and check the checks engine grades the site against. | GET /sites/{siteId}/checks/rubric |
New analysis (factors)
Organizations moved to the new analysis (rolled out gradually) see a slightly different tool set and different payloads, with the same REST mapping as the switched endpoints:
analyze_pageandget_page_reportreturn a factor table: every factor with its verdict, measurement and threshold, grouped by area, plus the recommendations once written. There is no score or grade. Passing factors are summarized; passinclude_passing: trueto list them.analyze_pagere-judges content only when it changed (unchanged content spends no allowance), andforcere-runs it anyway.get_page_reportreturns the page's latest analysis (404-styleno analysiserror when the page has none, including a page that only has a legacy analysis).find_pagestakesmin_factors_passing_pct(0-100) in place ofgrade.min_score/max_scorestill filter the opportunity (demand) score.get_opportunitiesrows show factors passing (for example36/41 factors passing, ornot analyzed). A row'sscoreis the demand rank, not an analysis score.get_rubricreturns the factor list instead of components and grade bands.write_recommendationsis added (see the table). An analysis whoserecommendations_statusisnot_requestedhas findings but no recommendations yet.- The
checks_*tools (checks_run,checks_analyze_page,checks_get_report,checks_regenerate_recommendations,checks_get_rubric) are not available to these organizations: they are neither listed nor callable. Useanalyze_page,get_page_report,write_recommendationsandget_rubric.
Everything else is unchanged.
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 a crawled snapshot, saved body content, or staged field drafts, it's analyzed directly (or its existing report is returned), so a page made with
create_pageand written withupdate_contentis analyzed without a crawl. A discovered page with none of those is crawled first. Aplannedor hand-added page with no content returnspage_not_live/no_contentinstead of crawling a URL that may not exist. - 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, so confirm uncertain paths with the user before calling analyze_page with a URL. If one slips through, remove it with delete_page.
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 (returning page_not_found/site_not_found otherwise), and recrawl_page only accepts page_id; it doesn't resolve a URL.
Create new content
To take a content idea from nothing to a reviewed draft without publishing anything:
create_pagewith a working title (the path defaults to its slug, as in the app) and the idea aspurpose.keyword_researchon a seed keyword, thenupdate_pageto save the primary and secondary keywords.generate_content_brief, thenupdate_content_briefto accept sections (their headings go into the body).- Write the body with
update_content(pass the brief'scontent_version_idasbase_version_id) and the title, meta description and H1 withset_field_draft. run_checks(free), thenanalyze_pageto review the saved content. No crawl is needed.
Plan a new site
To plan a whole site before it exists:
plan_sitewith the user's description of the business. Show them the proposed pages.edit_site_planto remove, rename, re-slug or add pages, or to regenerate the set from a revised brief.assign_planned_keywordsonce the user confirms the set. Check on it withget_site_planif it is still running.- Build each page as in Create new content (steps 3-5): briefs, body and fields work on planned pages.
- When the site is published,
go_live_sitewith its domain. After the crawl, the pages are analyzed like any live page.
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 few additions and behaviors are specific to the MCP layer:
invalid_argumentsmeans the tool call itself was malformed (bad zod input). Fix the arguments and retry.generation_cap_reachedmeans the page has used its 15 AI generations for the current analysis (generate_fixand revisions). Re-run the analysis to get a fresh set.generate_content_brief,generate_fixandgenerate_content_draftwait up to ~45 seconds. If the generation is still running, they returnstatus: "processing"with ajob_id. Poll it withget_job, then read the result:get_content_brieffor a brief, or callgenerate_fix/generate_content_draftagain with the returnedaction_id/rewrite_id(this resumes the wait and doesn't start a new generation).page_not_live/no_contentmeananalyze_pagewas asked to analyze aplanned(no live URL yet) or hand-added page that has no crawled or saved content. Write the body withupdate_content(or stage fields withset_field_draft) and analyze again, or, if the page is live, callrecrawl_pagefirst.analyze_page's crawl and analysis waits share a single ~45-second server-side budget for the whole call (not ~90s per step), so 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.