Local dashboard that pulls together how your web and app projects are found and how they monetize — Google Search Console, Umami, Google Play Console, ASO keyword research (via a local RespectASO instance), RevenueCat and AdMob (including ad mediation by network) — in one place, plus an MCP server so Claude Code can query the same data live from any project.
No SaaS, no monorepo, no server process beyond next dev: a local Next.js app backed by a single SQLite file.
- Local dashboard — sidebar with every project, a homepage overview and a per-project page (SEO, analytics, Play Store reviews, ASO keywords, ad/subscription revenue, opportunities, sync log)
- SEO opportunities — automated detection of low-CTR queries, striking-distance keywords, cannibalization, declining/growing pages, and more
- ASO research — keyword popularity/difficulty/opportunity scoring via a local RespectASO instance, with a reusable cross-project keyword cache
- App monetization — RevenueCat (MRR, subscribers, trials) and AdMob (ad revenue, impressions, clicks, and a per-ad-network mediation breakdown), Android + iOS summed per app
- MCP server — call live GSC/Umami data and manage projects from any Claude Code session as native tools
- SQLite storage —
data/openfindability.dbvia Drizzle ORM, a single local file with full history (no server, no manual setup) - Demo data — seed and explore without any real credentials
pnpm install
pnpm seed:demo
pnpm devOpen http://localhost:3000.
pnpm install
cp .env.example .env
pnpm run doctor
pnpm devThen configure only the connectors you need — every one is optional and independent.
Google Search Console / Play Console use a Google service account JSON file. Put it under secrets/google/ and point .env to it:
GOOGLE_SERVICE_ACCOUNT_FILE=./secrets/google/search-console-service-account.jsonUmami only needs the API base URL and key:
UMAMI_BASE_URL=https://api.umami.is/v1
UMAMI_API_KEY=...RespectASO is optional and local:
RESPECT_ASO_BASE_URL=http://localhostRevenueCat uses a V2 secret key:
REVENUECAT_API_KEY=sk_xxxAdMob needs interactive OAuth2 (no service account support) — see docs/guide/admob-setup.md for the full walkthrough:
pnpm run admob:auth # one-time, mints ADMOB_REFRESH_TOKEN (needs a real browser)
pnpm run admob:apps # lists every AdMob app id on the accountpnpm run project:add -- \
--name "Example Site" \
--slug example-site \
--type web \
--url https://example.com/ \
--gsc sc-domain:example.comFor an app with ASO research and monetization:
pnpm run project:add -- \
--name "Example App" \
--slug example-app \
--type app \
--app-store-track-id 1234567890 \
--respect-aso-app-id 3 \
--aso-countries it \
--aso-keywords "main keyword,secondary keyword" \
--revenuecat-project-id proj_xxx \
--admob-app-id ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy \
--admob-app-id-ios ca-app-pub-xxxxxxxxxxxxxxxx~zzzzzzzzzzpnpm run sync # sync GSC/Umami/Play Console/RevenueCat/AdMob for every project
pnpm run sync:aso # import configured RespectASO keywords (opt-in, rate-limited)
pnpm run report -- example-app allFor store listing research that combines RespectASO with GSC query language:
pnpm run research:aso -- \
--slug example-app \
--gsc-slug example-site \
--url-contains example.com/app \
--query-contains "main keyword" \
--keywords "main keyword,secondary keyword,online,offline"Research reports are written to project/<slug>/reports/, which is ignored by git. ASO keyword observations are also cached in the database, so later research can reuse recent keyword + country results across projects. Use --refresh to force a new RespectASO lookup.
OpenFindability exposes an MCP server so Claude Code can query your analytics live from any repo.
Install tsx globally:
npm install -g tsxAdd to ~/.claude/settings.json:
{
"mcpServers": {
"openfindability": {
"command": "tsx",
"args": ["/absolute/path/to/OpenFindability/mcp/server.ts"]
}
}
}On Windows use the full path to tsx.cmd:
{
"mcpServers": {
"openfindability": {
"command": "C:\\Users\\<user>\\AppData\\Roaming\\npm\\tsx.cmd",
"args": ["C:\\path\\to\\OpenFindability\\mcp\\server.ts"]
}
}
}Restart Claude Code. The server starts automatically — no process to keep running.
| Tool | Description |
|---|---|
list_projects |
List configured projects and their slugs |
get_project_summary |
Live GSC + Umami + opportunities in one call |
get_gsc_stats |
Live GSC data: clicks, impressions, top queries, top pages |
get_umami_stats |
Live Umami visitors and pageviews for a date |
get_page_keywords |
All GSC queries ranking for a specific page URL |
compare_periods |
Compare current vs previous period with deltas |
get_opportunities |
SEO opportunities from last sync |
list_gsc_properties |
GSC properties accessible to the service account |
create_project |
Add a new project (also creates its folder structure) |
update_project |
Update project fields |
delete_project |
Remove a project and all its data |
Live tools (get_gsc_stats, get_umami_stats, get_project_summary, get_page_keywords, compare_periods) call the APIs in real time — no sync needed.
See docs/guide/mcp-server.md for full setup and usage.
pnpm run doctor # check connector config, flag stale syncs
pnpm seed:demo # seed demo data
pnpm dev # start the dashboard
pnpm run sync # sync gsc/umami/play_console/revenuecat/admob for every project
pnpm run sync:aso # sync ASO keywords (opt-in)
pnpm run admob:auth # one-time AdMob OAuth2 setup
pnpm run admob:apps # list AdMob app ids on the account
pnpm run research:aso -- --slug <project-slug>
pnpm run report -- <slug> <gsc|aso|monetization|all>
pnpm run db:studio # open Drizzle Studio (local GUI SQL browser)
pnpm run db:generate # regenerate migrations after editing lib/db/schema.ts
pnpm typecheck
pnpm buildv0.1 uses manual sync only.
- GSC imports the last 30 days by default (Search Console data has ~2 day delay), plus device/country/searchAppearance breakdowns and sitemap status.
- Umami and AdMob/RevenueCat import yesterday's data.
- ASO stays opt-in (
pnpm run sync:aso) — rate-limited and cached upstream, so it never runs as part of the defaultpnpm run sync. - Every import keeps
rawJson. Connector runs are logged toconnectorRuns. - Re-running a sync the same day replaces that day's row per project/source instead of duplicating it — different days accumulate, so history is preserved.
- Page-level GSC data is stored separately from query-level data.
All data lives in data/openfindability.db, a local SQLite database accessed through Drizzle ORM (lib/db/schema.ts). No server process, no extra infra — still a single local file, just relational and queryable (pnpm run db:studio) instead of a hand-rolled JSON blob.
These are intentionally ignored by git:
.env
data/ ← openfindability.db (+ WAL/journal files)
project/ ← per-project reports, context, notes
private-notes/
secrets/
See docs/operations/sensitive-files.md for details.
MIT