Skip to main content
Glama
README.md
<h1 align="center">AdLens</h1>

<p align="center">
  <strong>Your AdMob numbers, without the AdMob console.</strong><br/>
  A fast AdMob analytics dashboard, plus an MCP server so your AI assistant can query the same live data.
</p>

<p align="center">
  <a href="https://adlens.moreproductive.in"><strong>Live app</strong></a> ·
  <a href="#connect-your-ai-assistant-mcp">MCP setup</a> ·
  <a href="#self-hosting">Self-host</a> ·
  <a href="https://github.com/ys-pro-duction/AdLens/issues">Issues</a>
</p>

<p align="center">
  <img alt="Free" src="https://img.shields.io/badge/price-free-brightgreen" />
  <img alt="Read-only AdMob access" src="https://img.shields.io/badge/AdMob%20access-read--only-blue" />
  <img alt="MCP: Streamable HTTP" src="https://img.shields.io/badge/MCP-Streamable%20HTTP-black" />
  <img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-lightgrey" />
</p>


<p align="center">
  <img src="docs/media/adlens-mcp-demo.gif" alt="Demo: asking Claude a question and getting live AdMob answers through the AdLens MCP server" width="800" />
  <br/>
  <em>Ask a question. Claude calls AdLens. Live AdMob answers.</em>
</p>

---

## What is AdLens?

The AdMob console shows you totals. **AdLens shows you causes.**

Connect your AdMob account once (read-only) and you get:

1. **A fast web dashboard** for revenue, impressions, eCPM, mediation and country breakdowns.
2. **An MCP server** so Claude, Cursor, Codex and other AI clients can answer questions from the same live data, such as *"Which ad format has the best eCPM this month?"*

It is free, read-only, and stores none of your report data. You can use the [hosted version](https://adlens.moreproductive.in) or [run your own](#self-hosting).

## Features

### Dashboard

- **Live, not synced:** fetched from the AdMob API when you open a page.
- **Today at a glance:** today, yesterday, last 7 days and this month, with impressions and eCPM.
- **Period comparisons:** every KPI shows its change vs. the previous period.
- **Sortable breakdowns:** by day, app, ad unit, format, mediation source or country.
- **Per-app deep dives** with the same depth, plus dark and light themes.

<p align="center">
  <img src="docs/media/dashboard.webp" alt="AdLens dashboard showing revenue, eCPM and country breakdowns" width="800" />
  <br/>
  <em>The AdLens dashboard (sample data).</em>
</p>

### MCP server

Five read-only tools that any MCP client can call:

| Tool | What it does |
| --- | --- |
| `account_overview` | Lists your connections and apps (id, name, platform, package name) |
| `revenue_summary` | Totals plus daily or weekly series, using presets or explicit dates |
| `breakdown` | Revenue by app, ad unit, format, country, ad source (mediation) or date |
| `top_movers` | Period-over-period movers, with the previous period chosen automatically for presets |
| `list_ad_units` | Your ad unit inventory |

Example prompts to try:

- *"Revenue summary for the last 30 days"*
- *"Top movers by country, this week vs last week"*
- *"Break down mediation revenue for my top app"*
- *"Which ad format has the best eCPM this month?"*

## Getting started (hosted)

Setup takes about a minute.

1. **Sign in** at [adlens.moreproductive.in](https://adlens.moreproductive.in) with an email one-time code or Google. A password is optional.
2. **Connect AdMob.** You approve one Google OAuth consent screen with the `admob.readonly` scope.
3. **Use it your way.** Open the dashboard, or generate an API key and connect your AI client (below).

## Connect your AI assistant (MCP)

Generate an API key on the [**API Keys page**](https://adlens.moreproductive.in/app/api-keys) of the dashboard. Keys look like `ak_live_...` and are shown **once**, so copy yours right away. Then add AdLens to your client.

The MCP endpoint is:

```
https://adlens.moreproductive.in/mcp
```

If you [self-host](#self-hosting), replace the domain with your own. AdLens is a standard **Streamable HTTP** MCP server authenticated with a Bearer token, so any client that supports a remote URL with custom headers will work.

<details open>
<summary><strong>Claude Code</strong></summary>

```bash
claude mcp add --transport http adlens https://adlens.moreproductive.in/mcp \
  --header "Authorization: Bearer ak_live_..."
```

Or add it to `.mcp.json`:

```json
{
  "mcpServers": {
    "adlens": {
      "type": "http",
      "url": "https://adlens.moreproductive.in/mcp",
      "headers": { "Authorization": "Bearer ak_live_..." }
    }
  }
}
```

</details>

<details>
<summary><strong>Claude Desktop</strong></summary>

Claude Desktop's config file has no native URL + header support, so bridge it with [`mcp-remote`](https://www.npmjs.com/package/mcp-remote).

Edit `claude_desktop_config.json`:

- macOS: `~/Library/Application Support/Claude/`
- Windows: `%APPDATA%\Claude\`
- Linux: `~/.config/Claude/`

```json
{
  "mcpServers": {
    "adlens": {
      "command": "npx",
      "args": [
        "-y", "mcp-remote",
        "https://adlens.moreproductive.in/mcp",
        "--header", "Authorization: Bearer ak_live_..."
      ]
    }
  }
}
```

**On Windows**, use `npx.cmd` instead of `npx` as the command:

```json
{
  "mcpServers": {
    "adlens": {
      "command": "npx.cmd",
      "args": [
        "-y", "mcp-remote",
        "https://adlens.moreproductive.in/mcp",
        "--header", "Authorization: Bearer ak_live_..."
      ]
    }
  }
}
```

</details>

<details>
<summary><strong>Codex</strong></summary>

In `~/.codex/config.toml` (Windows: `%USERPROFILE%\.codex\config.toml`):

```toml
[mcp_servers.adlens]
url = "https://adlens.moreproductive.in/mcp"
http_headers = { "Authorization" = "Bearer ak_live_..." }
```

</details>

<details>
<summary><strong>Cursor, VS Code, Windsurf and other clients</strong></summary>

For Cursor, edit `~/.cursor/mcp.json` (Windows: `%USERPROFILE%\.cursor\mcp.json`):

```json
{
  "mcpServers": {
    "adlens": {
      "url": "https://adlens.moreproductive.in/mcp",
      "headers": { "Authorization": "Bearer ak_live_..." }
    }
  }
}
```

For stdio-only clients, use the `mcp-remote` bridge shown in the Claude Desktop section.

</details>

<details>
<summary><strong>Antigravity</strong></summary>

In `~/.gemini/antigravity/mcp_config.json` (Windows: `%USERPROFILE%\.gemini\antigravity\mcp_config.json`), or via the MCP Store panel → Manage MCP Servers → View raw config:

```json
{
  "mcpServers": {
    "adlens": {
      "serverUrl": "https://adlens.moreproductive.in/mcp",
      "headers": { "Authorization": "Bearer ak_live_..." }
    }
  }
}
```

</details>

<details>
<summary><strong>OpenCode</strong></summary>

In `opencode.json` (project) or `~/.config/opencode/opencode.json`. This path is the same on Windows (`%USERPROFILE%\.config\opencode\opencode.json`), not `%APPDATA%`. Setting `oauth: false` makes OpenCode use your Bearer key instead of trying OAuth sign-in first.

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "adlens": {
      "type": "remote",
      "url": "https://adlens.moreproductive.in/mcp",
      "enabled": true,
      "oauth": false,
      "headers": { "Authorization": "Bearer ak_live_..." },
      "timeout": 30000
    }
  }
}
```

</details>

> **Tip:** to test the endpoint without an AI client, run the [MCP Inspector](https://github.com/modelcontextprotocol/inspector): `npx @modelcontextprotocol/inspector`

## Self-hosting

AdLens is multi-tenant by design and easy to run yourself. You need:

- Node.js (LTS) and [pnpm](https://pnpm.io), or just Docker
- A Postgres database (the included Docker Compose file starts one)
- A **[MojAuth](https://www.mojoauth.com)** project for sign-in (email OTP + Google). You'll need its API key.
- A **Google Cloud OAuth client** with the AdMob API enabled and the `admob.readonly` scope

### 1. Clone and configure

```bash
git clone https://github.com/ys-pro-duction/AdLens.git
cd AdLens
cp .env.example .env   # then fill in the values below
```

Generate the random secrets with:

```bash
openssl rand -hex 32
```

| Variable | Required | Description |
| --- | --- | --- |
| `APP_URL` | yes | Public URL of your instance (use your `https` domain in production) |
| `DATABASE_URL` | yes | Postgres connection string |
| `MOJOAUTH_API_KEY` | yes | Your MojAuth project's key (email OTP + Google sign-in) |
| `SESSION_SECRET` | yes | Random secret used to sign session cookies |
| `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET` | yes | Google OAuth client for connecting AdMob |
| `ENCRYPTION_KEY` | yes | 32-byte hex key used to encrypt refresh tokens at rest |
| `OAUTH_STATE_SECRET` | no | Separate secret for signing OAuth state |
| `TRUST_PROXY` | no | Set to `1` when running behind nginx so rate limits see real client IPs |
| `HOST` | no | Bind address. Use `127.0.0.1` behind a reverse proxy |
| `MOJOAUTH_AUDIENCE` | no | Optional audience check for MojAuth tokens |
| `MOJOAUTH_ISSUERS` | no | Override the allowed MojAuth token issuers |

`.env.example` is kept current and has the full annotated list.

In your **Google Cloud OAuth client**, register `<APP_URL>/api/admob/callback` as an authorized redirect URI. In your **MojAuth dashboard**, register the callback URL it shows you.

### 2a. Run with Docker (everything)

```bash
docker compose up --build
```

### 2b. Run locally for development

```bash
pnpm install
docker compose up -d db   # Postgres only
pnpm db:migrate
pnpm dev                  # API on :3000, web on :5173
```

### 3. Production

Build from the **repo root** (building a single package can leave a stale `packages/db/dist`):

```bash
git pull && pnpm install && pnpm db:migrate && pnpm build
```

In production the API serves the built web app (`apps/web/dist`), so there is a single process to run. Run it under a process manager of your choice (the hosted instance uses pm2, with Postgres in Docker).

Behind nginx (or any reverse proxy):

- Set `APP_URL` to your `https` domain
- Set `TRUST_PROXY=1`
- Bind the app to localhost with `HOST=127.0.0.1`

> **Note:** rate limits are held in memory per process, so AdLens is designed to run as a single process. If it is exposed directly (no proxy), leave `TRUST_PROXY` unset, because `X-Forwarded-For` can be spoofed.

## Fair-use limits

AdLens is free, with no plans and no billing. Per-IP rate limits protect the service:

| Endpoint | Limit |
| --- | --- |
| Dashboard API (`/api`) | 300 requests / minute |
| MCP server (`/mcp`) | 15 requests / minute |

If you self-host, these limits are yours to adjust in the source.

## Architecture

```mermaid
flowchart LR
  A[Vue dashboard] --> B[Express API]
  C[MCP clients] --> B
  B --> D[Live AdMob layer<br/>60-min cache · single-flight]
  D --> E[(Google AdMob API)]
  B --> F[(Postgres<br/>identity · encrypted keys )]
```

There is no worker, no warehouse and no stored metrics. Reports are fetched from Google on demand and aggregated in JS.

## Tech stack & layout

TypeScript monorepo (pnpm workspaces):

```
apps/web        Vue 3 + Vite SPA (ECharts): dashboard, reports, mediation, API keys, settings
apps/api        Express API + stateless MCP server; serves web/dist in production
                (live AdMob serving layer: apps/api/src/live)
packages/db     Drizzle schema + migrations (multi-tenant: tenant_id everywhere)
packages/admob  AdMob OAuth + reporting API client
packages/crypto AES-256-GCM secrets, API key generation/hashing, scrypt
packages/shared Shared TypeScript types and AdMob constants
```

## Disclaimer

AdLens is an independent project and is not affiliated with, endorsed by, or sponsored by Google. AdMob and Google are trademarks of Google LLC.

## License

[MIT](LICENSE)