Skip to main content
Glama
adilsonicjunior

youtube-analytics-mcp

README.md
# youtube-analytics-mcp

A local, **read-only** MCP server that gives Claude access to your YouTube channel's private Analytics data — views, watch time, retention, subscribers, traffic sources, audience demographics, revenue, and thumbnail impressions/CTR. Not just what any public API key can already see.

Nothing in this server can edit, upload, publish, or delete anything on your channel. See [SECURITY.md](SECURITY.md) for the full security review.

## Requirements

- Node.js 22+
- A Google account that owns (or manages) the YouTube channel you want data for
- macOS, Linux, or WSL (the `npm run auth` browser flow uses the `open` command)

## Setup checklist

Follow these in order. Steps 1–4 happen in Google Cloud Console; steps 5–8 happen on your machine.

### 1. Create a Google Cloud project

Go to [console.cloud.google.com](https://console.cloud.google.com/) and create a new project (or pick an existing one you're comfortable using).

### 2. Enable three APIs

In your project, go to **APIs & Services → Library** and enable each of these:

- [ ] **YouTube Data API v3**
- [ ] **YouTube Analytics API**
- [ ] **YouTube Reporting API** (only needed for thumbnail impressions/CTR — see [below](#a-note-on-impressions-and-ctr))

### 3. Configure the OAuth consent screen

Go to **APIs & Services → OAuth consent screen**.

- [ ] User type: **External** (unless you have a Google Workspace account, in which case Internal also works)
- [ ] Fill in the required app name / support email fields
- [ ] Add the Analytics scopes when prompted (or skip — the app requests them directly, this screen just needs to exist)
- [ ] **Publish the app to Production.** This is the step people skip and then hit a wall: apps left in "Testing" mode only allow sign-in from accounts you've explicitly added as test users, *and* their refresh tokens expire after 7 days, meaning you'd have to redo step 6 every week. Publishing to Production (without submitting for Google's verification review) is fine for a personal tool — Google will show an "unverified app" warning when you sign in, and you click **Advanced → Go to [your app name] (unsafe)** to proceed. That's expected and safe for your own app.

### 4. Create OAuth credentials

Go to **APIs & Services → Credentials → Create Credentials → OAuth client ID**.

- [ ] Application type: **Desktop app**
- [ ] Give it any name
- [ ] Copy the **Client ID** and **Client Secret** it generates — you'll need them in step 5

No redirect URI needs to be registered here; this server binds an ephemeral local port at auth time and Google accepts any loopback address for Desktop-type clients.

### 5. Install and build

```bash
git clone <this-repo-url>
cd youtube-analytics-mcp
npm install
npm run build
```

### 6. Configure your credentials

```bash
cp .env.example .env
```

Edit `.env` and paste in the Client ID / Client Secret from step 4:

```env
GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-client-secret
```

`.env` is gitignored — it will never be committed. Optional settings:

- `GOOGLE_API_KEY` — not required for any current tool, leave blank unless you extend the server yourself.
- `REVENUE_CURRENCY` — defaults to `USD`. Set to your AdSense payout currency (e.g. `BRL`) if you'd rather see revenue figures in that currency; Google converts server-side.

### 7. Authenticate

```bash
npm run auth
```

This opens your browser for Google sign-in and stores a refresh token at `~/.youtube-analytics-mcp/token.json` (permissions locked to your user only, never in the repo). You only need to do this once — the server refreshes the access token automatically afterward.

Verify it worked:

```bash
npm run auth:status
```

You should see `Authenticated` and your channel's name.

### 8. Point Claude Code at it

Add to your MCP config, using the **absolute path** to this project's `dist/index.js`:

```json
{
  "mcpServers": {
    "youtube-analytics-channel": {
      "command": "node",
      "args": ["/absolute/path/to/youtube-analytics-mcp/dist/index.js"]
    }
  }
}
```

Restart Claude Code (or reload MCP servers) and you should see the tools below available.

## Available tools

| Tool | What it does |
|---|---|
| `health_check` | Confirms the server is up. |
| `get_channel_overview` | Views, watch time, retention, subscribers, revenue for a date range or preset (`last_7_days`/`last_28_days`/`last_90_days`/`last_365_days`). |
| `list_videos` | Uploaded videos with metadata, filterable by publish date range and long-form vs. Shorts. |
| `get_video_analytics` | Deep-dive analytics for one video. |
| `get_top_videos` | Rank videos by any metric (views, watch time, retention, subscribers, revenue, impressions, CTR). |
| `get_daily_performance` | Day-by-day time series. |
| `get_traffic_sources` | Views/watch time by traffic source (search, suggested, Shorts feed, external, etc.), channel-wide or per video. |
| `get_audience_breakdown` | Audience by country, age group, or gender. |
| `get_revenue_analytics` | Revenue totals or breakdown by video/day. Returns `available: false` rather than fabricating numbers if revenue data isn't accessible. |
| `compare_periods` | Two date ranges compared, with absolute + percentage change. |
| `get_impressions_and_ctr` | Thumbnail impressions and click-through rate. **Async** — see below. |
| `run_custom_report` | Escape hatch for ad-hoc queries, restricted to an allowlist of metrics/dimensions. |

## A note on impressions and CTR

YouTube does **not** expose thumbnail impressions or CTR through the interactive Analytics API (`reports.query`) under any dimension/filter combination — this was verified directly against the API, not assumed from docs. That data only exists in YouTube's **bulk "Reach report"**, a separate async job API:

1. The first call to `get_impressions_and_ctr` registers a recurring reporting job with Google.
2. Google takes **24–48 hours** to produce the first report, then continues producing new ones roughly daily.
3. Every call to `get_impressions_and_ctr` (or `get_top_videos` sorted by impressions/CTR) syncs any newly available reports into a local cache at `~/.youtube-analytics-mcp/reach-cache.json`, then answers from that cache.

Until the first report lands, these tools return `impressions: 0`, `impressionsCtr: null`, and a `note` explaining why. This is expected on first use, not a bug.

## Troubleshooting

- **"Access blocked" during `npm run auth`**: your OAuth consent screen is still in Testing mode. Go back to step 3 and either add your account as a test user or publish to Production.
- **`NotAuthenticatedError` on server start**: run `npm run auth`.
- **Revenue always `0`**: either the channel isn't monetized, or the numbers are genuinely zero for that period. The tool never fabricates revenue — check `get_revenue_analytics`'s `available` field for a real permission/access failure vs. real zeros.
- **`get_impressions_and_ctr` / impressions-sorted `get_top_videos` return nothing**: check `dataCoverage` in the response. If `earliestDate` is `null`, the bulk report job hasn't produced its first report yet (can take up to 48h after the very first call).

## Testing

```bash
npm test
```

Runs unit tests (Node's built-in test runner) covering date/period validation, ISO-8601 duration parsing, CSV parsing, Analytics report row mapping, and period-comparison math (including the zero-division edge case). These are pure-function tests only — they don't mock live Google API calls or OAuth token refresh; those paths were validated manually against a real channel during development.

## Security

See [SECURITY.md](SECURITY.md) for the full threat model and OWASP Top 10 review. Short version: everything is read-only, all secrets stay on your machine outside the repo, and every user-supplied value that reaches a Google API call is validated first.

## License

MIT — see [LICENSE](LICENSE).