npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

anchor-seo-mcp

v0.9.2

Published

MCP server for the Anchor SEO platform — GSC Page Indexing history, Screaming Frog crawl data, and ranked technical-SEO audits for Claude and other MCP clients.

Readme

Anchor SEO — MCP Server + CLI

An MCP server and a CLI that expose the Anchor platform — the canonical audit report (#772, migs 090–095), GSC indexing + Screaming Frog data, and safe actions — to LLM tools: Claude Desktop, Claude Code, Cowork, and any other MCP client or agent shell.

Why both surfaces (product decision, 2026-07-01): in practice APIs/CLIs work better with LLM agents (a shell call needs no client integration and --help is the schema), while MCP is what customers expect to configure. Both are thin transports over the same data layer — one implementation, two surfaces; REST is a third wrapper, not a fork. New capability goes in the data layer first: db.ts for Postgres-backed reads/actions, raw.ts for the raw GCS tier (#993).

The agent loop this enables

get_audit_report(property)          → ranked, traffic-weighted findings
get_finding_urls(finding.params)    → the affected URLs (fix them in the repo)
recrawl_issues(property, snapshot)  → verify the fix (action, gated)

Tools

Read (both modes — local pg or the hosted REST API, org-scoped by the key): | Tool | What it returns | |------|-----------------| | get_audit_report | The canonical audit report: trust status, coverage, top-50 ranked findings with severity, traffic-weighted score, evidence, trend, opportunity.estimated_traffic, machine-readable recommended_action. Defaults to latest snapshot vs previous. | | list_audit_snapshots | Crawl snapshot dates usable with get_audit_report | | get_finding_urls | URL drill for an issue finding — takes the finding's recommended_action.params verbatim | | get_uncrawled_urls | URL drill for coverage findings (GSC-indexed pages the crawl missed) | | upload_gsc_capture | Register a GSC Page Indexing snapshot captured from the person's own signed-in browser (base64 export ZIPs keyed SUMMARY / INDEXED / drill-down item_key). Stored exactly like a scraper run; the importer processes it within minutes. Pair with the anchor-seo-gsc-capture skill | | upload_sf_crawl | Send a Screaming Frog crawl run on this machine to the platform: pass the export folder (issues_overview_report.csv required, internal_html.csv/.ndjson, crawl_overview.csv, Issues:All CSVs); the server reads it from disk and lands it at the fleet's GCS path, so the Cloud Function imports it like any fleet crawl. Both modes. | | get_steering | The organisation's standing instructions for its AI — role, tone, priorities, ignore rules, report recipients and cadence — plus a rendered briefing. First call of a session (the skills do it). Both modes. | | set_steering | Merge changes into the organisation's steering (only the fields passed change; lists replace). Recorded with who changed it. Both modes; any scope. | | publish_report | Publish a report into Anchor (0.9.0, #1189): markdown or a self-contained HTML page, one slug per report, republishing appends a version; returns the report URL where the organisation reviews and comments. body_path reads a file on the machine the MCP server runs on, in both modes (0.9.2); alt_body / alt_body_path attach the same report in the other format (HTML main view + markdown alternate, mig 242). Answers needs_confirmation when the steering's publish_confirm_kinds / publish_confirm_without_property match, until re-called with confirm true (recorded on the version). Refused when the organisation's steering has report_publishing_enabled false; an archived slug needs restore true | | archive_report | Soft-delete a published report (hidden from lists, closed to comments; versions and comments kept) or restore it with restore true (0.9.2, mig 242, #1202) | | list_reports | The organisation's published reports: slug, title, kind, status (draft / in_review / final), version, property, snapshot, open comments, alternate formats, URL; include_archived shows archived ones too | | get_report_comments | The review comments on a report version, threaded, with their {block_id, quote} anchors and authors (real signed-in identities) — read before the next audit/monitor run, apply, republish, then resolve | | resolve_comment | Mark a comment resolved after applying it (or reopen it) | | report_issue | Report a problem with the platform itself (wrong data, missing tool, slow, confusing, error). Stored per org + key, mirrored as a GitHub issue labelled reported-by-ai when the server has a token (or the operator's gh CLI is logged in) | | list_reported_issues | Reports previously filed through report_issue (your org only in remote mode) — check before filing a duplicate | | list_properties | Active properties + account, latest snapshot, last job status, SF auto-crawl flag (remote: the key's org only) | | get_pipeline_status | Freshness/health overview across the visible properties (anchor-seo-audit step 2) | | list_audit_ready | Snapshot-ready poll: per property, latest GSC snapshot + whether its crawl wave is terminal (anchor-seo-monitor trigger read) | | list_snapshots | Available GSC snapshot dates for a property | | get_property_overview | Indexed / not-indexed / total URLs per segment at a snapshot (true GSC totals) | | get_issue_breakdown | Per-category indexing issue counts at a snapshot | | get_sf_crawls | Recent Screaming Frog crawl jobs (status, auto-trigger). property is optional locally, required in remote mode | | get_tracked_issues / get_tracked_issue_report / get_validation_status | Tracked-issue (root cause) reads, mig 100 |

Read (local mode only — operator concerns): | Tool | What it returns | |------|-----------------| | list_crawl_configs | Available SF crawl configs (name, description, org-default) | | get_property_crawl_config | Which config(s) a property uses + auto-crawl flag |

Raw data tier (#993, both modes): | Tool | What it returns | |------|-----------------| | get_data_dictionary | The steering doc for raw analysis: sampling caps, paired snapshot dates, segment overlap, forward-fill, import caveats | | list_raw_exports | Manifest of raw files: GSC export ZIPs per (snapshot, segment, category) + SF crawl jobs with stored CSVs | | get_gsc_export | A raw GSC export verbatim — the actual Google-produced CSVs (Chart/Table/Metadata), 200KB cap | | query_crawl_data | One read-only DuckDB SELECT over a crawl's raw CSVs (tables = CSV basenames; 500-row cap; external access disabled) | | get_crawl_file_urls | Crawl CSV manifest with 15-minute presigned download URLs (signing-capable deployments; URL-less manifest otherwise) |

Safe actions (only when MCP_ALLOW_ACTIONS=true): | Tool | Effect | |------|--------| | recrawl_issues | Act on a finding's recommended_action.action_type="recrawl" (#936): queue per-issue SF crawls for a snapshot (⚠️ hits the live site) | | queue_gsc_refresh | Queue a GSC scrape for a property (idempotent per snapshot) | | queue_sf_crawl | Queue an SF crawl; pass max_urls for a capped test crawl (⚠️ hits the live site) | | cancel_sf_crawl | Cancel a pending/active SF crawl (the crawler halts within ~15s, #1022) | | pause_property_crawls | STOP all crawling of a property: cancel every in-flight job + disable auto-queue until resumed (local mode) | | resume_property_crawls | Re-enable auto-queued crawls after a pause (local mode) | | assign_property_crawl_config | Set a property's crawl config(s) + toggle auto-crawl (assign a conservative config to mitigated sites) |

CLI

Same tools as commands, JSON on stdout (node dist/cli.js help):

export INDEXING_DATABASE_URL=postgresql://...
node dist/cli.js audit groundworks.com                # full report, latest vs previous
node dist/cli.js finding-urls groundworks.com \
  --snapshot 2026-06-11 --issue-type <uuid> --page-types "Directory: /locations/,Parametered"
node dist/cli.js uncrawled-urls wearfigs.com --snapshot 2026-06-11
MCP_ALLOW_ACTIONS=true node dist/cli.js recrawl groundworks.com   # action, gated

Install (customers — npx quickstart)

The server is published as anchor-seo-mcp (the plain anchor-mcp name was already taken on npm). Customers run it in remote mode: two env vars, no database credential, everything scoped to their org by the API key.

With Claude Code:

claude mcp add anchor-seo \
  --env ANCHOR_API_URL=https://anchor-api-ipaersbhoq-uc.a.run.app \
  --env ANCHOR_API_KEY=ansk_... \
  -- npx -y anchor-seo-mcp

Or in a project's .mcp.json (Claude Code) / claude_desktop_config.json (Claude Desktop — same mcpServers shape):

{
  "mcpServers": {
    "anchor-seo": {
      "command": "npx",
      "args": ["-y", "anchor-seo-mcp"],
      "env": {
        "ANCHOR_API_URL": "https://anchor-api-ipaersbhoq-uc.a.run.app",
        "ANCHOR_API_KEY": "ansk_..."
      }
    }
  }
}

The key is provisioned per org with create_mcp_api_key (stored in Secret Manager); gw-groundworks is the reference remote-mode org. First run is slow (npx installs the package and its DuckDB/GCS deps, which remote mode never touches at runtime — slimming them out is a known follow-up); after that it starts from the npx cache.

Add --env MCP_ALLOW_ACTIONS=true only for keys carrying the actions scope.

Browser capture (0.4.0): Search Console has no API for Page Indexing, and the platform no longer holds anyone's Google session. The anchor-seo-gsc-capture skill fetches the export ZIPs from a signed-in browser in seconds and hands them to upload_gsc_capture (or POST /v1/properties/{property}/gsc-captures, or anchor gsc-capture-upload --dir). The hosted API accepts that POST from the https://search.google.com origin so a page can send the bundle directly. Local mode needs GCS write credentials for the exports bucket (GCS_BUCKET_NAME, default tightship-gsc-data) and the unzip binary.

Local crawl (0.5.0): the Local tier's crawls run on the person's own Screaming Frog. The anchor-seo-local-crawl skill writes a GSC reason's URL list to a file (get_gsc_export with write_urls_to), runs Screaming Frog headless in list mode with the fleet's exact export flags (--export-tabs "Internal:HTML,Response Codes:All,Page Titles:All,Meta Description:All" --save-report "Crawl Overview,Issues Overview" --bulk-export "Issues:All"), and uploads the output folder with upload_sf_crawl. The upload creates an sf_crawl_jobs row (crawl_mode='manual', triggered_by='local-upload', status uploaded) and writes the files to gs://tight-ship-consulting-crawl-results/crawls/<org>/<property>/<job>/crawl-results/, overview last; the existing process-sf-csv Cloud Function completes the job within a couple of minutes. An internal_html.ndjson (what Screaming Frog's own MCP exports) is converted to the CSV the importer parses. Verified end to end 2026-09-25 on bigbrandtire.com (6-URL list, 30 files, complete in 60 s).

Steering (0.5.0, mig 208): one organization_steering row per organisation holds the customer's standing instructions for their own Claude: role, tone, priorities[], ignore_rules[], report_recipients[], report_cadence, notes. get_steering returns it with a rendered briefing and is_default when nothing is set; set_steering merges a patch and records updated_by. REST GET/PUT /v1/steering on the key's org. The audit and monitor skills read it as step 0 and it outranks their built-in reporting style. The onboarding kit's CLAUDE.md is now generic — Tight Ship's own orders moved into its steering row — so a customer's kit no longer carries our internal instructions. The org tier only; the global → org → property model is #774.

Issue reporting (0.3.0): report_issue works on any key. The hosted API mirrors reports to GitHub when deployed with the anchor-github-issues-token secret (a fine-grained PAT with Issues: read/write on this repo, exposed as GITHUB_ISSUES_TOKEN); local operators get the same mirror from a logged-in gh CLI or by setting GITHUB_ISSUES_TOKEN in .env. Without either, reports are still stored in product_feedback and listed by the monitor.

Scheduled monitoring

Install as a Claude Code plugin (preferred since 0.6.0). The marketplace source lives in plugin/ and is mirrored to the public repo bengoodsell/anchor-plugins by scripts/publish-plugin.sh:

/plugin marketplace add bengoodsell/anchor-plugins
/plugin install anchor-seo@anchor
/plugin configure anchor-seo@anchor   (enter your ansk_ key)
/anchor-seo:setup

/plugin configure collects the ansk_… key (plugin userConfig, stored in Claude Code's credential store), registers the anchor-seo and sf-local MCP servers (as plugin:anchor-seo:anchor-seo and plugin:anchor-seo:sf-local; a project .mcp.json entry of the same name takes precedence), and injects the five-line standing context through a SessionStart hook. The plugin carries no recipe text and almost never needs a new version; the server (@latest) and its prompts update on their own. The zip kit under onboarding-kit/ remains for Claude Desktop (which has no plugins) and for machines that cannot reach a marketplace.

Incidents and live URL inspection (0.7.0, mig 217). get_incidents (and GET /v1/properties/{property}/incidents) answers "did something break on a page that earns traffic, and does Google know yet": a NEW or WORSENING audit finding in a category Google acts on (response codes, directives, canonicals) whose URLs carry at least the organisation's incident_click_threshold clicks/28d (steering, default 50). Each incident lists its top URLs with Google's last Page Indexing category, the crawler's status/indexability/canonical, and, when auto_inspect_incidents is on, a live URL Inspection verdict. inspect_url now works in both modes (POST /v1/properties/{property}/inspect): it uses the property's own Search Console account's refresh token from gsc-oauth-tokens (falling back to the other accounts), keeps a per-property daily budget (INSPECT_DAILY_CAP, default 200) in gsc_url_inspections, and serves results younger than 24 h from there. The monitor recipe leads with incidents; the weekly digest shows them in red.

Recipes are served, not shipped (0.6.0). The four recipes under skills/ (audit, monitor, gsc-capture, local-crawl) are the single source: the hosted API serves them at GET /v1/skills, and the MCP server fetches them at startup (falling back to the copies bundled in this package) and registers them as MCP prompts. Claude Code lists prompts as slash commands, so a customer runs /mcp__anchor-seo__audit <property> (from a .mcp.json install; /mcp__plugin_anchor-seo_anchor-seo__audit from the plugin) with no skill files on their machine, and a recipe fix reaches every customer on their next session with nothing to redistribute. get_briefing (and GET /v1/briefing) is the first call of a session: the standing working instructions, the organisation's steering, the recipe list, and whether this machine's client is behind the published version (client.update_available + update_note). The REST layer records each key's client version from the X-Anchor-Client header (mig 214; anchor-seo-cli key-versions for the operator view, and Settings → API keys shows it per key).

A workspace that still carries the kit's old .claude/skills/ copies keeps working (/anchor-seo-audit is the same text); it just stops receiving updates until the files are deleted.

Then schedule the monitor with whatever runs your agent on a cron:

# Claude Code scheduled task / routine — the whole prompt is one line:
#   "/mcp__anchor-seo__monitor"   (or: "Run the anchor-seo monitor recipe for all my properties.")
# Or plain cron + headless CLI:
0 13 * * 1-5  cd /path/to/project && claude -p "Run the anchor-seo monitor recipe for all my properties."

State lives in .anchor-seo/monitor-state.json next to the project, so runs are idempotent: a new GSC snapshot (roughly fortnightly per property) produces one changes-only report; every other run is a single "no new snapshots" line. GSC publishes on its own clock — daily schedules just catch the new snapshot within a day; they don't create more data.

Setup (development, from the repo)

cd features/mcp-server
npm install
npm run build

Two modes (#940 org-scoped auth)

LOCAL (internal ops): set INDEXING_DATABASE_URL → direct Postgres, the full tool set including the crawl-config reads and the ops actions. This is god-mode; never ship this configuration to a customer.

REMOTE (customer): set ANCHOR_API_URL + ANCHOR_API_KEY (an ansk_… org key, mig 096) → the customer surface registers: list_properties, the pipeline/snapshot reads (get_pipeline_status, list_audit_ready, list_snapshots, get_property_overview, get_issue_breakdown, get_sf_crawls — added in 0.2.0 so the shipped skills run on a customer key), the audit + tracked-issue reads, the raw data tier, and recrawl_issues. Every call goes through the hosted REST layer (https://anchor-api-ipaersbhoq-uc.a.run.app), and the key scopes everything to the customer's org — get_pipeline_status and list_audit_ready return only that org's properties. Neither the DB credential nor GCS access ever reaches the customer's machine — raw SQL runs server-side, and bulk file access is via short-lived presigned URLs. Local-only tools: list_crawl_configs, get_property_crawl_config, and the ops actions other than recrawl_issues.

Set MCP_ALLOW_ACTIONS=true to enable the mutating tools in either mode (off by default); in remote mode the API additionally requires the key's actions scope — defense in depth.

REST API (dist/http.js)

The hosted trust boundary (live at https://anchor-api-ipaersbhoq-uc.a.run.app) — holds INDEXING_DATABASE_URL, authenticates Authorization: Bearer ansk_…, filters to the key's org:

GET  /v1/properties
GET  /v1/pipeline-status                      (org's properties only)
GET  /v1/audit-ready?since=YYYY-MM-DD         (org's properties only)
GET  /v1/properties/:property/snapshots
GET  /v1/properties/:property/overview?snapshot=
GET  /v1/properties/:property/issue-breakdown?snapshot=
GET  /v1/properties/:property/crawls
GET  /v1/properties/:property/audit?snapshot=&compare=
GET  /v1/properties/:property/audit/snapshots
GET  /v1/properties/:property/finding-urls?snapshot=&issue_type_id=&page_types=&limit=
GET  /v1/properties/:property/uncrawled?snapshot=&limit=
POST /v1/properties/:property/recrawl        (requires 'actions' scope)
GET  /v1/data-dictionary
GET  /v1/properties/:property/raw-exports?snapshot=
GET  /v1/properties/:property/raw-exports/gsc?snapshot=&category=&segment=
GET  /v1/properties/:property/crawl-files?snapshot=
POST /v1/properties/:property/query-crawl    {sql, snapshot?}   ('read' scope — read-only SQL)

Issue keys (raw key shown exactly once; hash-only storage; revoke via revoked_at):

SELECT create_mcp_api_key('<organization_id>', 'customer-name', '{read}');          -- read-only
SELECT create_mcp_api_key('<organization_id>', 'customer-name', '{read,actions}');  -- + recrawl

Claude Desktop

Add to claude_desktop_config.json (Settings → Developer → Edit Config):

{
  "mcpServers": {
    "anchor-seo": {
      "command": "node",
      "args": ["/absolute/path/to/tightship_ai/features/mcp-server/dist/index.js"],
      "env": {
        "INDEXING_DATABASE_URL": "postgresql://...",
        "MCP_ALLOW_ACTIONS": "false"
      }
    }
  }
}

Claude Code

claude mcp add anchor-seo \
  --env INDEXING_DATABASE_URL=postgresql://... \
  --env MCP_ALLOW_ACTIONS=false \
  -- node /absolute/path/to/tightship_ai/features/mcp-server/dist/index.js

Deployment requirements (raw tier)

GCS access tries the @google-cloud/storage SDK first (ADC / service account) and falls back to the gcloud CLI. The hosted REST deployment should run with a service-account credential (GOOGLE_APPLICATION_CREDENTIALS or workload identity) — that also enables presigned URLs on /crawl-files, which user ADC cannot sign. get_gsc_export additionally needs the unzip binary in the image. Local dev works with either gcloud auth application-default login (SDK) or plain gcloud auth login (CLI fallback).

Notes

  • Read-only by default. Enable actions deliberately.
  • Headline counts use chart_url_count (true GSC totals), not the 1,000-URL export sample (row_count).
  • Crawls hit live sites — do not queue_sf_crawl bot-mitigated properties without a conservative config (see the SF config tooling work).
  • v1 is stdio transport. An authenticated HTTP/SSE transport (for remote/Cowork) is a planned follow-up.