akzar1el/digestseo-geo
Track brand citations and AI visibility across ChatGPT, Claude, Perplexity, Gemini, Grok, and Google AI Overviews through the local mcp-geo server.
Changelog
All notable changes to this project are documented here. The format is loosely based on Keep a Changelog.
Unreleased
Fixed
- Cursor plugin MCP metadata now launches the published local stdio package instead of routing users to the public Worker endpoint, which is not configured as a turnkey fresh-scan service.
- Windsurf guidance now recommends local stdio or a configured self-hosted Worker, and Cursor/Claude plugin manifests are synchronized to
0.3.7.
[0.3.21] - September 22, 2026
Changed
- OAuth-protected hosted MCP traffic now uses the stateless SDK-v2
createMcpHandlerpath with legacy stateless compatibility and explicit Host/Origin validation; the old Durable Object binding is retained only as a migration hold. - All twelve MCP tool schemas now use lean example-led descriptions and complete input-parameter descriptions, with regression coverage to prevent context bloat and missing guidance from returning.
[0.3.20] - September 22, 2026
Fixed
- Provider HTTP calls now have a 90-second timeout, so a stalled upstream API cannot hang a local or hosted visibility scan indefinitely.
- Authenticated
/admin/run-livescans can setwait_for_completion: trueso manual/operator refreshes stay attached to the HTTP request until per-engine service-binding work finishes instead of depending on Cloudflare's post-responsewaitUntil()window.
[0.3.19] - September 22, 2026
Added
- Added opt-in Google AI Mode visibility scans through SerpAPI, with separate per-prompt requests and citation/source capture when returned.
- Added
update_brandso local MCP users can correct tracked-brand identity, competitors, aliases, exclusions, and refresh cadence without replacing active prompts or historical runs.
Changed
track_brandandupdate_brandnow acceptrefresh_frequency: manual; self-hosted scheduled refresh skips manual brands while explicitrefresh_brandremains available.
[0.3.18] - September 21, 2026
Added
- Added opt-in xAI Grok visibility scans using grok-4.6 with required Web Search grounding, citation capture, local and self-hosted Worker key plumbing, and synchronized MCP/client metadata.
[0.3.17] - September 21, 2026
Added
- Local MCP users can set an exact 1-50-question measurement set with
set_prompts; the operation preserves historical runs, rejects duplicate/blank questions, and is a no-op when the active set is already identical.
[0.3.16] - September 21, 2026
Fixed
- Gemini now defaults to
gemini-3.1-flash-lite, avoiding the Gemini 2.5 access restriction Google applies to some new projects while preserving the same GenerateContent API integration.
[0.3.7] - September 19, 2026
Fixed
- Prompt generation now persists exactly the requested number of unique prompts or leaves the existing prompt set untouched, protecting fixed-scope audit runs from silently undersized prompt sets.
[0.3.6] - September 18, 2026
Added
- Gemini CLI extension metadata plus direct one-click install guidance for VS Code and LM Studio.
Fixed
- Live ChatGPT scans use grounded web search, domain-lookalike mentions no longer inflate brand/competitor scoring, and the conflicting GitHub Sponsors rail was removed.
- OpenAI model/pricing documentation was refreshed against the live search-backed implementation.
[0.3.5] - September 17, 2026
Added
- Owned privacy-policy and directory-readiness disclosure for local/Desktop distribution.
- Transparent audit score-calculation documentation.
Fixed
- Perplexity visibility migrated to the current Agent API.
- Cursor/plugin installs use the published local stdio package instead of the unconfigured public Worker, with provider-key configuration preserved.
- MCPB bundles remain production-only.
[0.3.4] - September 15, 2026
Fixed
- Replaced the local/MCPB
better-sqlite3dependency with Node's built-innode:sqlite, eliminating third-party native.nodebinaries that Claude Desktop on macOS can reject under Library Validation. - The one-click MCPB and local stdio runtime now require Node.js 22.13+, where
node:sqliteis available without an enable flag. - Refreshed the MCP SDK and security-sensitive transitive pins so the production dependency tree audits with zero known vulnerabilities at release qualification time.
- Official MCP Registry metadata no longer advertises the public
geo-mcp.digestseo.com/mcpremote while that endpoint is not configured as a turnkey fresh-scan service; the npm stdio package remains the canonical registry package.
[0.3.3] - September 10, 2026
Added
- Optional EUR 99 one-time AI Visibility Audit for teams that want a client-ready baseline without operating mcp-geo themselves; the open-source package remains free.
- Direct prefilled audit-request links with deterministic source markers on the repository README, plus a repo-native announcement for the same bounded offer.
Changed
- Package, MCP Registry, MCPB, Cursor, Claude plugin, and Worker-reported version metadata are synchronized on
0.3.3. - The public
/auditimplementation prefills the exact offer and buyer inputs; later sample-report and methodology polish is merged in the repository but still awaits production Worker promotion.
[0.3.2] — July 27, 2026
Published to npm as @digestseo/mcp-geo.
Added
- Hosted MCP tool metadata: hosted tools use
visibility.*namespaces and provide typed input/output schemas; the local stdio CLI retains flat tool names. - Dedicated Worker storage configuration: Worker examples and migration documentation target the dedicated
mcp-geo-dbD1 database. - Marketplace metadata: Cursor and Claude Code plugin manifests, along with refreshed MCP Registry, MCPB, Cline, and Smithery metadata for the DigestSEO-branded server and hosted endpoint.
Changed
- Package, registry, MCPB, and Worker-reported metadata are synchronized on version
0.3.2and the@digestseo/mcp-geopackage identity. - Production transitive dependencies are pinned to patched Hono,
fast-uri, andbody-parserversions.
[0.3.0] — July 2026
A second distribution path: a local stdio CLI where users bring their own API keys, published to npm as @digestseo/mcp-geo, plus registry/marketplace metadata. The Cloudflare Workers path keeps every 0.2.1 behavior — the accuracy and security fixes now live in the runtime-agnostic core shared by both.
Added
- Local stdio CLI (
npx -y @digestseo/mcp-geo): the same MCP tools backed by a local SQLite database (~/.digestseo/digestseo.sqlite, override withDIGESTSEO_DB_PATH). Engines run inline and sequentially — no fan-out needed locally. All logging goes to stderr; stdout is the JSON-RPC channel. - Local brand-management tools (CLI only):
track_brand(creates a brand + generated prompt set; falls back to three starter prompts withoutANTHROPIC_API_KEY; accepts the per-brandaliasesandexclude_termsfrom 0.2.1),list_brands(brands with active prompt counts),generate_prompts(regenerate a brand's prompt set via Claude Haiku). On Workers deployments these operations remain behind theX-Seed-Secret-gated/admin/*routes; the Worker MCP surface still exposes exactly the original six tools. src/core/— runtime-agnostic core shared by the Worker and the CLI: engine callers, scoring, prompt generation, content-gap analysis, brand seeding, andregisterTools/registerLocalManagementTools.src/db/types.ts(Dbcontract),src/db/d1.ts(D1 adapter),src/db/sqlite.ts(better-sqlite3 adapter with a_migrations-tracked migration runner). Both adapters carry the 0.2.1 brand columns (aliases_json,exclude_terms_json) and the partial-run-aware visibility-history query.- Packaging/metadata: npm
bin+mcpName,server.jsonfor the official MCP registry (npm package + templated Worker remote in one manifest),manifest.json+.mcpbignorefor the MCPB desktop extension,Dockerfile,glama.json,llms-install.md, README Quick Install section, CI + release-publish GitHub Actions workflows. - Unit tests for the sqlite adapter and core seeding, alongside the 0.2.1 matching/citations/scoring suites (
npm run test:unitruns all of them), and a stdio smoke test that drives the built CLI over JSON-RPC (npm run test:stdio).
Fixed
- Fallback seed prompts no longer contain a literal
$CATEGORYplaceholder — it is substituted with the brand's category (or name) before insert. Affects the Worker's/admin/seedfallback path too.
Changed
hashPromptkeeps 0.2.1'sHASH_FIELD_SEP = '\x1f'field separator (cache keys are unchanged from 0.2.1).- npm manifest:
dependenciesnow lists only what the published CLI actually needs at runtime (@modelcontextprotocol/sdk,better-sqlite3,zod); Worker-only packages (@cloudflare/workers-oauth-provider,agents) aredevDependencies— wrangler bundles them into the Worker regardless, andnpx @digestseo/mcp-geoinstalls stay small. This supersedes 0.2.1's "manifest tells the truth" arrangement, which predated the npm package.
[0.2.1] — June 2026
A security + accuracy pass ahead of wider distribution.
Added
- Optional
CONNECT_SECRETsecret. When set, the browser step of the OAuth connect flow shows a one-field form and only completes when the secret matches. Without it the OSS build keeps its auto-completing single-dev-user flow — which means anyone who knows the worker URL can connect an MCP client and callrefresh_brand(spending your engine API credits). Recommended for every deployment whose URL is shared anywhere. - Per-brand
aliasesandexclude_terms(migrations/0005_brand_alias_exclude.sql,Brandinsrc/db.ts,SeedBrandInputinsrc/seed.ts). Aliases are extra terms that always count as a mention; exclude terms suppress the bare-word match on the brand name and domain root — so "Monday" the brand stops matching "monday" the weekday — while the full domain and aliases still count.extractCitationsnow matches through amentionsTermSetterm set; its exported signature is unchanged. Apply 0005 to add thealiases_json+exclude_terms_jsoncolumns; existing brands read as empty arrays and behave exactly as before. SECURITY.md— security model, trust boundaries, and private vulnerability reporting.- GitHub Actions CI (
.github/workflows/ci.yml):tsc --noEmit+ unit tests on every push and PR. - Unit test suite (
npm run test:unit, Node test runner + tsx):tests/matching.test.tscovers brand/competitor mention matching (excluded bare words, full-domain matches that survive exclusion, root terms not matching inside a larger word);tests/unit/citations.test.tsandtests/unit/scoring.test.tscover citation extraction (extractCitations,hostMatchesDomain) and score aggregation (computeOverallScore). These are the pure functions every score flows through; the smoke suite still covers the deployed Worker end-to-end. migrations/README.md— how to apply migrations and why files must never be renumbered.- README: Claude Code connect instructions (
claude mcp add --transport http), architecture diagram, security section.
Fixed
- Word-boundary brand/competitor matching in
src/openai.ts.mentionsTermusedhaystack.includes(root), a raw substring test: a brand whose domain root is a common word (monday.com→ "monday",notion.so→ "notion") false-positived on the everyday word, and any term matched inside a larger word ("motion" inside "promotional"). Matching now uses Unicode-aware boundary checks —(?<![\p{L}\p{N}])…(?![\p{L}\p{N}])— for the brand name and the domain root, while the full domain is still accepted as a high-confidence substring. The exportedextractCitationssignature is unchanged. (The homograph case — a bare root term that is itself a common word, e.g. "monday"/"notion" — is handled via the new per-brandexclude_terms.) - Linked-citation checks require the exact brand domain or a subdomain of it. Previously a substring check meant
notacme.comcounted as a link toacme.com. Applies toextractCitations, the Perplexity and AI Overviews engine-citation merge, andget_citations'cited_urlselection — all through the new sharedhostMatchesDomainhelper. - NUL → unit-separator hash delimiter in
hashPrompt(src/openai.ts). The field join used two literal NUL (\x00) bytes, which made git classify the whole file as binary and refuse to render its diffs. It now joins with the ASCII unit separator through a namedHASH_FIELD_SEP = '\x1f'constant. Hash outputs change, so existingshared_prompt_cacherows simply miss and expire on their TTL — no cache clear or migration required. get_visibility_historymatchescheck_visibility's stance on partial runs. Runs interrupted mid-flight (stuck atin_progress) now count toward history viaCOALESCE(completed_at, started_at), and runs with zero ok rows are excluded entirely instead of charting as a fake score of 0.
Changed
- Docs (
README.md,SETUP.md) now recommend OpenAI + Anthropic (Claude) as the starting engine pair instead of leading with Gemini's free tier. Gemini capacity varies by model, project, and usage tier, so project-specific 429s can exclude Gemini rows from scoring; Google AI Overviews also frequently returnsNO_AI_OVERVIEW(scored as a zero). The cheapest documented path could therefore produce misleading first-run data. Gemini and SerpAPI stay documented as opt-in engines; engine availability is unchanged and remains key-driven (getAvailableEnginesuntouched). SEED_SECRETandCONNECT_SECRETcomparisons are constant-time.- MCP server version string now tracks the package version (was stuck at an older value).
- Runtime dependencies (
@cloudflare/workers-oauth-provider,@modelcontextprotocol/sdk,agents,zod) moved fromdevDependenciestodependencies— wrangler bundles either way, but the manifest now tells the truth.
[0.2.0] — May 2026
A correctness + architecture pass driven by an end-to-end debugging session on the production fork. Every fix is described in terms of what the prior version got wrong.
Added
migrations/0004_response_status.sql:status(ok/failed/skipped) anderror_messagecolumns onprompt_responses, plus an index onstatus. Failed engine calls used to writeraw_response='ERROR: ...'rows that downstream scoring treated as real zero-mention hits. They're now explicitstatus='failed'rows that aggregates exclude.POST /admin/run-engine— single-engine handler./admin/run-liveself-fetches into it once per engine so each engine runs in its own worker invocation with its own free-plan 50-subrequest budget. Idempotent on(run_id, engine).POST /admin/cleanup-failed-runs— one-shot deletion of legacy polluted rows that were written before the status column existed. Idempotent, optionalbrand_idfilter.bulkCacheGethelper insrc/db.ts— one D1 read for N prompt hashes instead of N separate reads.persistEngineRunhelper insrc/db.ts— one batched D1 write at the end of an engine run instead of per-prompt inserts + cache puts + per-chunk run updates.EnginePromptResultinterface — engines collect their per-prompt results in memory and flush in one batch.runEngineInProcessinsrc/engines.ts— the in-process dispatcher used by/admin/run-engine.Fetcherservice binding (env.SELF) inEnvandEnginesEnv, plus"services"block inwrangler.example.jsonc. The per-engine fan-out goes throughenv.SELF.fetch()because a public-URL fetch back to your ownworkers.devhostname trips Cloudflare's "Worker called itself" guard (error 1042) and never lands.SELF_URLenv var inwrangler.example.jsonc— the cron path needs to know the worker's canonical URL since it has no inbound request to derive an origin from.
Changed
- Each engine client (
src/openai.ts,anthropic.ts,perplexity.ts,gemini.ts,ai-overviews.ts) now uses the bulk pattern: hash all prompts up front,bulkCacheGetonce, run LLM fetches in CONCURRENCY=5 chunks, collectEnginePromptResult[], flush viapersistEngineRun. Per-invocation subrequest count drops from ~89 to ~26. getLatestCompletedRunanchors onEXISTS(prompt_responses with status='ok')instead ofruns.status='completed'. Partially-finished runs still surface their data instead of silently disappearing from MCP tool output.check_visibility,compare_competitors, andget_content_gapsuserun.completed_at ?? run.started_atso they accept in-progress runs.get_visibility_historyandget_citationsSQL queries filterpr.status = 'ok'in the join/where clause so failed/skipped rows can't pollute aggregates./admin/run-engineINSERT OR IGNOREs its runs row before calling the engine — D1's cross-region replication may not have caught up with/admin/run-live'sINSERT INTO runsby the time the self-fetch lands, and a missing parent row would crash the engine's batched insert withFOREIGN KEY constraint failed.runEnginesaccepts an optionalrequestargument and falls back tonew URL(request.url).originwhenSELF_URLisn't set, so HTTP-triggered code paths work even without the env var.
Fixed
- Subrequest-cap row loss: pre-fix, every engine deterministically wrote 10 rows out of 20 because chunk 3 hit the 50-subrequest cap. Now writes complete in one batch under the cap.
- Silent row drops from failed Gemini / AI Overviews calls: pre-fix the catch block's
insertPromptResponse(failed)was itself a D1 subrequest that could fail, leaving zero rows for the prompt. The bulk batch holds failure results in memory and writes them in the same batch as the successes. - Visibility tools dropping engines whose runs got stuck at
in_progress: relaxedgetLatestCompletedRunsurfaces them.
[0.1.1] — May 2026
- Removed
scripts/setup.sh— the bash script wasn't reliable across platforms (Git Bash on Windows in particular couldn't pipe stdin to wrangler's interactive prompts). Replaced with a fully manual, copy-pasteable walkthrough in SETUP.md as the canonical install path. - SETUP.md is now self-contained — every wrangler command, every interactive prompt response, and the most common failure modes are documented inline.
[0.1.0] — May 2026
Initial public release.
Added
- 5-engine support: ChatGPT (
gpt-4o-mini), Claude (claude-haiku-4-5), Perplexity (sonar), Gemini (gemini-2.5-flash-lite), and Google AI Overviews (via SerpAPI). - 6 MCP tools:
check_visibility,get_visibility_history,compare_competitors,get_citations,get_content_gaps,refresh_brand. - Opt-in engine availability — engines are enabled only when their API key is present.
- D1-backed storage: brands, prompts, runs, prompt_responses, shared_prompt_cache.
- Cloudflare Cron Trigger (every 6h) that respects per-brand
refresh_frequency(daily/weekly). - Admin routes:
/admin/seed,/admin/run-live,/admin/run-batch-submit,/admin/run-batch-collect,/admin/generate-prompts,/admin/trigger-cron-test. All gated bySEED_SECRET. - Smoke test suite at
tests/smoke.test.mjs(Node built-in test runner; no external deps). - Claude-Haiku-powered prompt generator and content-gap analyzer with deterministic fallback recommendations.