psyb0t

rankrat

Community psyb0t
Updated

Read Search Console, Bing Webmaster, GA4 and PageSpeed data through one self-hosted MCP server.

rankrat

CIcoverageversionlicenseDocker Pulls

Search Console, Bing Webmaster Tools, GA4 and PageSpeed each ship a dashboard.Dashboards answer the question you already knew to ask. The interesting ones —which queries actually caused last week's drop, is this page down because itlost rankings or because it fell out of the index, do Google and Bing agreesomething broke — need you to pull four exports and do the join yourself, in aspreadsheet, at 1am.

rankrat puts those APIs behind one MCP server so an agent does the joining. Youask; it goes and rats out whichever provider knows.

It's a rat, not a burglar. It answers only for the accounts and properties youlist in a boundary file, and no tool widens that from inside a session.Read-only unless you deliberately turn writes on, at which point the write toolsappear and stay inside the same boundaries. Nothing here crawls a site, tracks acompetitor, or touches a domain you haven't verified — it reads what theproviders already hold about you.

Speaks MCP over stdio and Streamable HTTP, plus a REST API for callers thatdon't speak MCP.

Status: alpha. Everything documented here works against real accounts; thetool surface is still free to move.

Contents

  • Quick start
  • Running it
  • Agent integrations
  • Write capability
  • Onboarding a new site
  • Credentials
  • Configuration
  • Verification
  • Security
  • Release information

Quick start

Rankrat ships as a Docker image; Docker is the only requirement. Create theworking layout in a directory of your choosing — Rankrat reads all three pathsas mounts and never writes outside them:

mkdir -p config oauth secrets/google secrets/bing secrets/indexnow secrets/rankrat
chmod 700 config oauth secrets secrets/rankrat

curl -fsSL https://raw.githubusercontent.com/psyb0t/rankrat/main/config/boundaries.json.example \
  -o config/boundaries.json
chmod 600 config/boundaries.json

install -m 600 /dev/null secrets/rankrat/http-bearer-token
openssl rand -base64 32 | tr -d '\n' > secrets/rankrat/http-bearer-token

The bearer secret authenticates /v1/ and /mcp for non-loopback HTTP; stdionever uses it, but the HTTP mode requires the file to exist.

Then edit config/boundaries.json to list only what Rankrat may read, put thecredentials for the providers you actually use at the paths inCredentials, and authorize Google if you configured it:

rankrat.sh auth-google --account-id google --print-authorization-url
rankrat.sh setup

setup is a live, read-only verification gate. It checks configured provideraccess; it does not create a provider resource or submit an IndexNow URL. Itreports the missing local file, OAuth grant, provider permission, or configuredresource that needs attention. It is the one mode that reads .env, where theRANKRAT_LIVE_* selectors choose which providers it checks — leave theselectors for unused providers empty and their checks skip.

Running it

rankrat.sh          # MCP over stdio
rankrat.sh http     # REST + Streamable HTTP MCP on 127.0.0.1:8080

rankrat.sh is a wrapper around docker run: it resolves themounts, publishes the port, and applies the container hardening flags. Installit once —

curl -fsSL https://raw.githubusercontent.com/psyb0t/rankrat/main/rankrat.sh \
  -o rankrat.sh
less rankrat.sh                       # it is ~250 lines; read before installing
chmod +x rankrat.sh
sudo mv rankrat.sh /usr/local/bin/rankrat.sh

— or skip it entirely and run the image yourself. docker run is the supportedcontract and the wrapper is only convenience:

docker run -i --rm --init --read-only \
  --user "$(id -u):$(id -g)" \
  --cap-drop=ALL --security-opt no-new-privileges:true \
  --pids-limit 128 --memory 512m --cpus 1 \
  --tmpfs /tmp:rw,noexec,nosuid,size=32m \
  -e RANKRAT_READ_ONLY=true \
  -e RANKRAT_BOUNDARY_FILE=/run/config/boundaries.json \
  -e RANKRAT_OAUTH_TOKEN_ROOT=/run/oauth \
  --mount type=bind,src="$PWD/config",dst=/run/config,readonly \
  --mount type=bind,src="$PWD/secrets",dst=/run/secrets,readonly \
  --mount type=bind,src="$PWD/oauth",dst=/run/oauth \
  psyb0t/rankrat:latest stdio

Mount config/ as a directory, not config/boundaries.json as a single file.The image bakes /run/config owned by its own rankrat user, so a single-filemount leaves that directory in place and the boundary file then reads only whenthe container uid happens to match the host file's owner. Mounting the directoryreplaces it, and readonly still covers the file inside.

Swap stdio for http and add -p 127.0.0.1:8080:8080,-e RANKRAT_HTTP_HOST=0.0.0.0 and-e RANKRAT_HTTP_BEARER_SECRET_FILE=/run/secrets/rankrat/http-bearer-token.RANKRAT_HTTP_HOST=0.0.0.0 binds inside the container; -p is what keeps it onloopback. MCP is then at http://127.0.0.1:8080/mcp.

The wrapper reads RANKRAT_IMAGE, RANKRAT_BOUNDARIES, RANKRAT_SECRETS,RANKRAT_OAUTH, RANKRAT_ENV_FILE, RANKRAT_HTTP_PORT andRANKRAT_OAUTH_CALLBACK_PORT from the environment to point at non-defaultpaths. Those are host-side only — keep them out of .env, which goes straightto the container, where an unrecognized RANKRAT_* variable fails startup.rankrat.sh --help lists every mode.

On a checkout, the Make targets call the same wrapper against the locally builtimage, so make run and make run-http exercise the path above rather than asecond copy of it. See Verification and make help.

Agent integrations

The skill works in any agent that reads.agents/skills/, and installs natively in the clients below. An agent runs thepublished image directly and does not need the wrapper.

Claude Code

claude plugin marketplace add psyb0t/agents
claude plugin install rankrat@psyb0t

Codex

codex plugin marketplace add psyb0t/agents
codex plugin add rankrat@psyb0t

Installed this way the skill invokes as $rankrat:rankrat. Codex also picks itup on its own in any repo containing .agents/skills/, where it is $rankrat.

OpenClaw

The skill and the MCP bridge are published to ClawHub on every release:

openclaw skills install @psyb0t/rankrat
openclaw plugins install clawhub:@psyb0t/rankrat

The bridge covers both transports. It defaults tostdio, running the published image itself — set RANKRAT_CONFIG_DIR (plusRANKRAT_SECRETS_DIR and RANKRAT_OAUTH_DIR when those are needed) and nothinghas to be running first. For a shared server, set RANKRAT_TRANSPORT=http andRANKRAT_URL, plus RANKRAT_AUTH_TOKEN if a bearer secret is configured.

Write capability

There is no approval ID, admin API, or second bearer token.

# Default: no write services, REST write routes, or MCP write tools exist.
RANKRAT_READ_ONLY=true

# Direct, boundary-limited writes become visible to the trusted caller.
RANKRAT_READ_ONLY=false

In read-only mode, write routes are not mounted and write MCP tools are absentfrom tools/list, so an LLM cannot discover or call them. In writable mode,all writes remain restricted to the explicit boundaries and the provider's ownpermissions. The regular HTTP bearer token protects /v1/ for non-loopbackHTTP; stdio access is controlled by who can start the process.

Writes cover IndexNow submission, Bing URL/sitemap/property changes, GoogleIndexing notifications, Search Console site/sitemap changes, and new-siteonboarding. They are marked destructive/non-idempotent in MCP.

Onboarding a new site

onboard-site creates a GA4 property, a Search Console property and a Bing sitefor one URL, then records their IDs in the boundary file. Two things are worthknowing before using it.

It cannot verify the site, and does not pretend to. Verification proves to aprovider that you own the domain; the token is issued by that provider's ownconsole and Rankrat holds no credential that can read it. So onboarding returnssuccess — meaning the three create calls were accepted — while the properties arestill unverified and returning no data. What you do next depends on the propertyform:

Property Methods it accepts
sc-domain:example.com DNS TXT record, and nothing else
https://example.com/ GA4 tag, HTML file, meta tag, DNS TXT
Bing Import from a verified Search Console property, XML file, meta tag, CNAME

Deploy the returned GA4 Measurement ID first. It is the one artifact Rankrathands you directly, and on a URL-prefix property it also satisfies Search Consoleverification on its own — after which Bing can simply import that property. Leastwork, no extra APIs.

The procedure is served rather than left implied, so an agent can walk you throughit instead of guessing: the rankrat://onboarding MCP resource, arankrat://onboarding/{site_url} template for one percent-encoded site, and anonboarding_guide tool returning the same document for clients without resourcesupport. All three are read-only and present even on a read-only server — knowingthe procedure is not a privilege. rankrat onboard-site prints the same stepswhen it finishes.

Agents cannot reach it unless you say so. It is the only operation thatrewrites the boundary file this server enforces, so RANKRAT_READ_ONLY=falsealone is deliberately not enough:

RANKRAT_READ_ONLY=false
RANKRAT_ALLOW_AGENT_ONBOARDING=true

Without the second switch site_onboarding_submit is absent from tools/listand its REST route is never mounted, however writable the rest of the server is.rankrat onboard-site ignores the switch — that is you at a terminal, not anagent in a session.

One caveat: if a later stage fails the earlier ones are not rolled back. A runthat creates the GA4 property then fails on Search Console leaves that propertyreal but absent from the boundary file, and re-running creates a second one.Check Analytics before retrying.

Credentials

secrets/, oauth/, .env, and config/boundaries.json are Git-ignored.Keep credential files owner-readable only and their directories owner-only.Never put a credential in source, YAML, Makefiles, or chat.

Purpose Local path
Google OAuth desktop-client JSON secrets/google/oauth-client.json
Google OAuth token record oauth/google.json
PageSpeed API key secrets/google/pagespeed-api-key
Bing Webmaster API key secrets/bing/api-key
IndexNow key secrets/indexnow/key
HTTP bearer secret secrets/rankrat/http-bearer-token

Google

  1. Create/select a project in Google Cloud Console.

  2. Configure Google Auth platform.If External/Testing, add the signing-in user underAudience.

  3. Enable Search Console,Analytics Data,Analytics Admin,Indexing, andPageSpeed.

  4. Create an OAuth Desktop app client atOAuth Clients, download itsJSON to secrets/google/oauth-client.json, configure boundaries, then run:

    rankrat.sh auth-google --account-id google --print-authorization-url
    

    The callback listens on 127.0.0.1:49152; set RANKRAT_OAUTH_CALLBACK_PORTto move it. Revoke the grant later withrankrat.sh revoke-google --account-id google.

The one consent flow requests Search Console management, Indexing, and GA4read/edit scopes. It does not bypass provider-side ownership. Grant that Googleuser the required Search Console and GA4 roles first.

PageSpeed

PageSpeed is the one Google surface that does not use OAuth at all. It takes aplain API key on the query string, so the consent flow above grants it nothingand there is no PageSpeed scope to request.

The key is optional. Without one, PageSpeed calls go out unauthenticated andGoogle applies a much tighter quota — fine for a handful of URLs, not for aregular sweep. Get one:

  1. Open Credentials in thesame project, then Create credentials → API key.

  2. On the new key choose Edit API key → Restrict key → API restrictions →Restrict key, and select PageSpeed Insights API only. An unrestrictedkey works against every API enabled on the project, which is not what youwant sitting in a file.

  3. Save the key on its own line, no quotes:

    install -m 600 /dev/null secrets/google/pagespeed-api-key
    printf '%s' 'YOUR_KEY' > secrets/google/pagespeed-api-key
    

Then set the account's pagespeed_api_key_file in config/boundaries.json tothe path inside the containersecrets/ is mounted read-only at/run/secrets, so the host file above is:

"pagespeed_api_key_file": "/run/secrets/google/pagespeed-api-key"

Leave that field unset to run keyless.

Bing and IndexNow

Verify each site in Bing Webmaster Tools,then create an API key under Settings → API Access and save it assecrets/bing/api-key.

IndexNow key generation and verification are the one part of setup that is notin the image — they run from a checkout, since neither is something a runningserver should be able to do:

make init-indexnow INDEXNOW_TARGET_ID=site INDEXNOW_HOST=example.com
make verify-indexnow-key INDEXNOW_TARGET_ID=site

Publish the generated <key>.txt on the public target host yourself. Theverifier requires exact key content over direct HTTPS, with no redirect. Skipthis section entirely if you are not submitting URLs to IndexNow.

Configuration

Copy config/boundaries.json.example and list only resources Rankrat may touch.Every account, site, GA4 property, child URL, and IndexNow host is validated atthe boundary; callers cannot choose provider origins or resources outside it..env.example leaves live-test selectors blank intentionally: set only theaccount and resource selectors for providers you configured, so rankrat.sh setup can verify those integrations without requiring credentials for theothers. HTTP mode requires the bearer-secret file even on loopback, and mountsit only into the running container.

Unbounded bootstrap mode

For a trusted agent that needs to discover or onboard a resource not yet listed,start Rankrat with both RANKRAT_READ_ONLY=false and RANKRAT_UNBOUNDED=true:

RANKRAT_READ_ONLY=false RANKRAT_UNBOUNDED=true rankrat.sh http

The same environment variables work with rankrat.sh for stdio MCP. Unboundedmode is reusable: stop the process, start it with those variables whenever atrusted agent needs another discovery/onboarding session, then restart withoutthem to return to normal bounded enforcement.

This keeps credential accounts fixed by the boundary file, but bypasses theresource allow-lists for Google, Bing, and PageSpeed. It does not expose acredential path, arbitrary provider origin, or IndexNow key target. Non-loopbackHTTP still requires the normal bearer secret.

The smallest bootstrap boundary still names the pre-mounted Google and Bingcredential accounts. In unbounded mode, use discovery, thensite_onboarding_submit with the discovered Google Analytics parent account ID.The operation creates the GA4 property, Search Console property, and Bing site,then atomically persists their exact IDs and URLs into boundaries.json. Restartwith RANKRAT_UNBOUNDED=false to enforce the now-expanded allow-list. You mayenable unbounded mode again later for another trusted onboarding session.

For this operation the parent directory holding boundaries.json must be ownedby the host user and owner-only (chmod 700 config); the file itself must notbe group- or world-writable. The chmod 700 in Quick startestablishes that, and rankrat.sh refuses to start an unbounded process againstan unsafe writable mount. Normal bounded runs keep the boundary file read-only.The Compose example intentionally stays bounded and mounts that file read-only;use rankrat.sh for a reusable unbounded session.

Unbounded mode is also why that run mounts the boundary file's directory at/run/config rather than the file itself — onboarding replaces the fileatomically, and a rename over a bind-mounted file is not possible. Running theimage by hand for an unbounded session needs the same substitution.

The authoritative REST contract issrc/rankrat/api/openapi.yaml. Runmake generate-openapi then make check-openapi after a spec change.The source lists the full writable contract; a running read-only server removeswrite routes from its served /openapi.json document.

Verification

From a checkout. Everything below runs in containers — the host needs onlyDocker and GNU Make:

make format
make lint
make test-unit
make test-contract
make test-integration
make test-security
make test-image

Live read checks use the shipped image and configured accounts:

make test-live-google-search-console
make test-live-google-analytics
make test-live-pagespeed
make test-live-bing
make test-live-http

make test-live-indexnow is a real external submission only when bothRANKRAT_READ_ONLY=false and RANKRAT_RUN_LIVE_INDEXNOW_SUBMISSION=true areset. Use an already-public harmless URL.

Security

  • Read-only is the safe default and removes write capability discovery.
  • Writable mode trusts the process caller while retaining fixed boundaries,request limits, provider permissions, fixed origins, and normal HTTP auth.
  • Provider responses are parsed into typed bounded results; credentials,OAuth records, raw provider bodies, and Bing key URLs are not returned/logged.
  • The production image is non-root; the Compose example uses loopback ports,dropped capabilities, no-new-privileges, read-only root, tmpfs, and explicitsecret mounts.

License

WTFPL. See LICENSE.

Release information

See CHANGELOG.md for release history andTHIRD_PARTY_LICENSES.md for the production image'sthird-party Python package attribution.

MCP Server · Populars

MCP Server · New

    DIDA-AI

    RollingGo Hotel MCP — Hotel Search & Booking

    Official DIDA Hotel Booking MCP Server. 14-year travel tech data stack, 2M+ hotels at wholesale rates, 40+ LLM compatible. Free unlimited calls for businesses & individual devs. Filter by location, date, star grade, guests & tags; pull real-time room types, pricing & cancellation rules.

    Community DIDA-AI
    micro

    mu

    A personal home server

    Community micro
    talivia-group

    Talivia Agent Kit

    Revenue-first website analytics installed and verified by AI agents through MCP

    Community talivia-group
    gura105

    Operational Ontology

    A minimal, readable reference implementation of the Operational Ontology pattern. Palantir Foundry is one implementation; this is the concept, minimized.

    Community gura105
    EllisMorrow

    Caelune

    Caelune (星野) — Local-first retrieval for private Markdown, PDF, and Tika documents, with a Windows desktop app and read-only MCP server.|本地优先的私人知识检索工具。

    Community EllisMorrow