npm / geo-new
CLI and MCP server
One npm package is both: the command you run in a terminal and the server a coding agent talks to. Keyless from the first call, a key when you want one, and the same audit the browser runs; it speaks to the HTTP API that /docs/api documents. The last two sections are about this site rather than the package: every page here is readable as Markdown and says where its own contract lives.
From a terminal
one line, no keynpx -y geo-new audit https://example.com
The first call mints a session and keeps it in your config directory. The audit starts, or attaches to one already running for that URL, and the measured rows land in about a minute. The report goes to stdout as Markdown and the progress goes to stderr, so the one can be piped and the other cannot. The AI synthesis keeps writing afterwards and never moves a score; --wait-for-ai waits for it.
- Every command
- --quiet · --verbose · --no-color · --help · --version
- Credentials
- $XDG_CONFIG_HOME/geo-new/credentials.json (0600, written atomically), or GEO_NEW_API_KEY for one process. A key is sent only to https or loopback origins.
- Local server
- GEO_NEW_API_URL=http://127.0.0.1:8787 points every command at a local geo.new, such as npm run dev:demo.
exit codes
frozen from 0.1; the lowest winsFrom an agent
MCP over stdioclaude mcp add geo-new -- npx -y geo-new@0 mcp
# Windows
claude mcp add geo-new -- cmd /c npx -y geo-new@0 mcp
# Cursor, Codex, Windsurf and other MCP clients
{ "mcpServers": { "geo-new": { "command": "npx", "args": ["-y", "geo-new@0", "mcp"] } } }The server runs inside the package, over stdio, against the same API. There is no hosted endpoint to configure and no key to paste: the first tool call mints a session exactly as the command line does, and a key created in any terminal is picked up on the next call.
- The loop
- audit_page, then get_fix_prompt, edit the page, then audit_page again with fresh: true. A non-fresh call may return the report measured before your change, marked cached with its age; every result carries a next step.
- Limits
- A rate limit is an ordinary result with the code, the wait and the call to make after it, never an error the agent has to parse. Re-reads are free.
- Trust
- Quoted page content inside a result is data from the audited site, never instructions. The server never sees page HTML; it sees snapshots and Markdown, exactly like the browser.
This site as Markdown
every document, negotiatedcurl -H 'Accept: text/markdown' https://geo.new/agents curl https://geo.new/agents.md
Every public document answers Accept: text/markdown with its own content as Markdown, and the same document sits at its .md path for a client that would rather not negotiate. Neither needs JavaScript: the HTML is prerendered and the Markdown is generated from that same markup at build time, so the two representations cannot drift apart.
- Both ways
- Negotiate on the canonical path, or add .md to it. The home page twins as /index.md; every other document appends .md to its own path.
- Headers
- A document carries Vary: Accept and a Link header whose rel=alternate names the other representation. An Accept header that neither type satisfies is a 406, never a silent fall back to HTML.
- Not documents
- Reports, shares and the audit view are the application, not public documents: noindex, no-store, and no Markdown twin. A report's Markdown comes from the API instead.
the twelve documents
canonical path · markdown twinDiscovery
five files and one headerWhat an agent can learn about this service without reading a page of it. Each of these is a plain file at a stable path, served to anything that asks.
- Link header
- Every document response advertises the guide (rel=describedby), the sitemap, the API catalog and the OpenAPI document (rel=service-desc), so one HEAD from any page reaches the contract.
- Dead ends
- A path that is no document answers 404 with a document saying so, in whichever representation was asked for, rather than 200 with an empty shell. /contact and /docs/agents are 301s to their canonical paths.
A path that looks like a website is not a dead end: geo.new/example.com audits that page, which is why an unrecognised path is classified before it is refused. The reference states which shapes are reserved; see /docs/api.
Keyless or keyed
no accounts either waygeo-new key create prints a key once and saves it for this machine; geo-new login <key> on another one. The same key can be made, listed and revoked in the browser at /docs/api#keys, where a browser can also claim a key so that reports made from a terminal open at their /r/ links there. Every plan still shares the ceiling everyone shares; the figures are in the reference under limits.
From a script
the same client, importedimport { createClient } from 'geo-new';
const geo = createClient({ apiKey: process.env.GEO_NEW_API_KEY });
const { audit_id } = await geo.audit('https://example.com', { fresh: true });
const { snapshot } = await geo.waitFor(audit_id, { mode: 'measured' });
console.log(snapshot.scores.catalog?.value);The client is what the command line and the MCP server are built on. It holds a session before any create, sends an Idempotency-Key on every one, retries server and network failures twice and never retries a rate limit. It does not validate a snapshot at runtime, so an additive server change never breaks a cached copy of the package; the types come from the published OpenAPI contract.
In CI
a gate and a trendnpx -y geo-new audit https://staging.example.com --fresh --out after.json --min-score 70 npx -y geo-new compare before.json after.json --allow-missing-baseline mv after.json before.json
- Gate
- --min-score fails the job (exit 6) below a floor; an unmeasured score counts as below unless --allow-unscored, so a page the audit could not read does not pass by accident.
- Trend
- Reports expire after seven days, so keep your own snapshots with --out and compare them offline. compare exits 7 on a drop; --allow-missing-baseline lets the first run through.
- Identity
- Hold a key in GEO_NEW_API_KEY. Keyed principals have a network bucket of their own, so a shared runner pool is not starved by strangers on the same address.