Skip to main content
Glama

yt-analytics-mcp

Owner-side YouTube analytics for AI agents: watch time, traffic sources, audience retention, playlist and podcast-series metrics, and episode-over-episode comparison. Read-only, all of it.

CI License: MIT Node Podcast X


An MCP server for the YouTube Analytics API v2. It gives an AI assistant the numbers only a channel owner can see — the ones behind the public view count. How long people actually watched, where they came from, where in a video they left, which playlist they were working through, and how a new episode is tracking against the last five at the same age.

Why this exists. Google ships official MCP servers for Workspace, Google Analytics, and BigQuery. It does not ship one for YouTube Analytics, and the community servers that claim to cover it are mostly wrapping the public Data API — which can tell you a video's view count and nothing about watch time, retention, or traffic sources, because that data is owner-only and needs OAuth. This server does the owner-side half properly and stops there.

It was built to run analytics for a podcast, which is why the playlist tooling is unusually complete. Nothing here is specific to that show; point it at any channel you own.

Tools

Nine tools. Every one is a read.

Tool

What it returns

API

yt_channel_info

Which channel the credential owns, with subscriber/view/video counts

GET youtube/v3/channels?mine=true

yt_channel_overview

Views, watch time, avg duration/percentage, subs gained/lost, likes, comments, shares — as a total or a day/month series

reports.query

yt_traffic_sources

Views and watch time by how viewers arrived (search, suggested, external, Shorts feed, subscriptions, playlists…)

reports.query insightTrafficSourceType

yt_top_videos

Videos ranked by any core metric, with titles

reports.query video

yt_video_performance

Metrics for videos you name, and which of them had no activity

reports.query video + filter

yt_audience_retention

The drop-off curve for one video, ~100 points sampled down

reports.query elapsedVideoTimeRatio

yt_geography

Views and watch time by country, with each country's share

reports.query country

yt_playlist_performance

Playlist starts, views per start, time in playlist, saves — the podcast-series view

reports.query playlist

yt_episode_race

Derived. Several videos re-indexed to days-since-their-own-publish, plus a leaderboard at the oldest age they all share

reports.query + publish dates

Side effects: there are none

Every tool is readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true. All four hints are stated explicitly on all nine tools rather than left to defaults, because MCP's destructiveHint defaults to true — an omitted hint tells a client to raise a delete-grade consent prompt for reading a view count.

That is not just a labelling convention. The HTTP client in src/client.ts has no write path: every method is a GET, and ids=channel==MINE is hardcoded rather than accepted from any tool input, so no argument an agent can pass will point this server at another channel or change anything on yours. The e2e test asserts the exact annotation object on each tool from a real tools/list, and includes a control proving that assertion can go red.

The derived view

yt_episode_race is the one tool that computes rather than reports, and it exists because raw totals cannot answer the only question that matters week to week: is this episode doing better than the last one? An episode published a month ago has simply had more days to accumulate views. Re-indexing each video to days since its own publish date removes that, so day 7 sits next to day 7.

Two details it gets right that a naive version would not: each curve is truncated at the video's real age rather than padded with its last value (padding makes a 3-day-old episode look like it flatlined for 25 days), and the leaderboard is pinned to the oldest day every video has reached, never to lifetime totals. Every response labels exactly which fields the server computed (computedFields) and returns the API's own per-day values alongside them.

Related MCP server: youtube-organic-mcp

Authentication

An OAuth installed-app credential — a saved refresh token — not a service account and not an API key. There is no API-key path to this data: watch time, retention, and traffic sources are owner-only, and Google will not serve them to anything but an OAuth token for an account that owns the channel.

Default location, shared on purpose with the sibling servers and any Google scripts you already run:

~/.config/gws/youtube_credentials.json

Set YT_ANALYTICS_CREDENTIALS_PATH to use a different file.

Scopes

Scope

Needed for

https://www.googleapis.com/auth/yt-analytics.readonly

Required. Every Analytics report.

https://www.googleapis.com/auth/youtube.readonly

Optional. Video titles, channel metadata, and publish dates — so yt_channel_info and yt_episode_race need it, and title resolution elsewhere degrades gracefully without it.

yt-analytics-monetary.readonly is accepted in place of the first; youtube and youtube.force-ssl are accepted in place of the second. Without the optional scope the server still starts and every Analytics tool still works — you get bare video IDs and a note explaining why, not a failure.

Minting the credential

  1. In Google Cloud Console, create an OAuth client of type Desktop app and download its client_secret.json.

  2. Enable both the YouTube Analytics API and the YouTube Data API v3 on that project.

  3. Set the OAuth consent screen's publishing status to "In production" — click Publish app. See the warning below; this is the step everyone skips.

  4. Run any standard google-auth-oauthlib installed-app flow requesting the scopes above and save the result to the path above. About ten lines of Python, one browser consent click.

⚠️ Publish the app, or your token dies in 7 days

Google's OAuth documentation: "A Google Cloud Platform project with an OAuth consent screen configured for an external user type and a publishing status of 'Testing' is issued a refresh token expiring in 7 days, unless the only OAuth scopes requested are a subset of name, email address, and user profile."

These scopes are not that subset. Leave the project in Testing and this server will work for a week and then start failing with an auth error that looks like a bug in the server. Switch the consent screen to In production and refresh tokens stop expiring.

You do not need Google to verify your app for this. yt-analytics.readonly and youtube.readonly are sensitive, not restricted, and an app using its own OAuth client for its own owner's data does not go through verification review — an unverified-app warning at the consent screen is expected, and you click through it. Do not use a shared or hosted OAuth client to dodge this: those hit a 100-new-user cap that cannot be reset.

Starting without a credential

The server boots and answers tools/list with no credential at all, so MCP inspectors can introspect it. Tool calls then return a setup pointer. Diagnostics go to stderr; stdout is the MCP transport and stays clean JSON-RPC.

Setup

Add to your MCP client config — .mcp.json for Claude Code, claude_desktop_config.json for Claude Desktop:

{
  "mcpServers": {
    "yt-analytics": {
      "command": "node",
      "args": ["/path/to/yt-analytics-mcp/dist/index.js"]
    }
  }
}

No env block is needed when the credential is at the default path.

Then ask your assistant: "Which YouTube channel am I connected to?", then "Show me traffic sources for the last 30 days," then "Race my last three episodes against each other."

Limitations

Read these. Several of them are things competing servers claim to do and cannot.

  • No impressions, no impressions CTR, no unique viewers. These exist in YouTube Studio and do not exist in the Analytics API. Each returns Unknown identifier (...) given in field parameters.metrics — verified live, not inferred from docs. Any MCP server advertising click-through rate from this API is advertising something it cannot deliver. If you need CTR, the Studio UI is the only source.

  • Data lags 2–3 days. End your ranges a few days before today or the last days come back as zeros. yt_episode_race defaults its as-of date to yesterday for this reason; pass as_of_date explicitly if you want a reproducible answer.

  • Owner-side only. This reads channel==MINE and nothing else. You cannot point it at a competitor's channel — not a limitation of this server, a limitation of the API, and the correct one.

  • Retention needs volume. YouTube withholds the retention report for videos below a watch-time threshold. yt_audience_retention says so rather than returning a bare empty array.

  • One video per retention call, enforced here. The API accepts a comma-separated list on the retention report and then silently returns only the first video's curve with HTTP 200 — verified live with two real owned video IDs. Google's docs say single-ID-only; the API does not enforce it. This server rejects lists so you never get one episode's numbers labelled as several.

  • Report-specific caps you will hit. Video-ranked reports cap at 200 rows and require a sort order (no sort is an HTTP 400). Month grouping requires the range to sit on whole-month boundaries. The server sends the sort automatically and checks month alignment before the request, so you should not meet these — but they are why some parameter combinations are not offered.

  • reports.query only; no Reporting API. The bulk YouTube Reporting API is a different shape: "You can start retrieving the report within 48 hours of the time that the job is created," and "API reports are available for 60 days from the time that they are generated." A two-day cold start and a rolling 60-day window is a batch-warehouse contract, wrong for an interactive agent. Deliberately out of scope; use the BigQuery path below if you want bulk history.

  • No caching, no stored history. Every call goes to Google. That is a deliberate simplification, not an oversight — see below.

API terms and data handling

Worth stating plainly, since an agent reading your analytics is exactly the case these rules were written for.

Storage. This server stores nothing. No cache, no database, no history file; responses go to the caller and are gone. YouTube's Developer Policies (§III.E.4.b) do permit storing Analytics API data long-term, so a future caching layer would not be prohibited — but the 30-day cap in §III.E.4.c/d applies to other data classes, and holding nothing keeps the question from arising.

Derived metrics. §III.E.4.h prohibits replacing API Data with independently calculated substitutes or using it to create new metrics. This server computes only plain arithmetic over YouTube's own numbers — running sums, a part-of-total share, a day difference — never a score, index, or composite rating. Every response that contains a computed value lists those fields under computedFields and returns the API's own values beside them, so nothing this server calculated can be mistaken for something YouTube reported. If you extend it, keep that line.

Other options

Nothing else fills this slot well, which is why this exists — but be precise about what the alternatives are.

You want

Use

Owner-side YouTube Analytics via MCP

this repo — no official Google server exists for it

YouTube public data (search, metadata, comments, uploads)

a Data-API server such as ZubeidHendricks/youtube-mcp-server — popular, but Data-API-centric; an API key can never reach owner-only metrics

YouTube analytics in a warehouse, with SQL

BigQuery Data Transfer's YouTube Channel connector plus Google's own BigQuery MCP — a genuinely good path if you already run BigQuery, at the cost of warehouse setup and transfer latency

The analytics siblings

Data

Server

YouTube Analytics

this repo

Google Workspace

gws-mcp-server

Search Console

gsc-mcp — same curated approach, including derived views like gsc_striking_distance

Google Analytics 4

googleanalytics/google-analytics-mcp — Google's own, read-only

BigQuery

googleapis/mcp-toolbox — Google's own

These are separate credential families, not one login. Workspace authenticates with gws auth login, Search Console with a webmasters OAuth credential, GA4 with Application Default Credentials scoped analytics.readonly, and this one with a yt-analytics.readonly OAuth credential. Nothing here shares a token with anything else.

Development

npm install
npm run build
npm test

Tests mock the HTTP layer and make no network calls. Assertions pin exact values — the request parameters built for each tool, the exact annotation object on each tool from a real tools/list — rather than shapes, and carve-outs carry a must-fail leg beside the must-pass one.

A separate live smoke test drives every tool against the real API with your real credential. It is not part of npm test:

node scripts/smoke.mjs

Contributing

Issues and pull requests welcome. If a YouTube Analytics report is worth wrapping, open an issue describing what it should return and the dimension/metric combination it maps to — and please verify that combination against the live API first, because the docs and the API disagree in places (the retention filter being the sharpest example). Keep the contract honest: every tool stays read-only, annotations stay explicit, computed fields stay labelled, and responses stay compact.

About

Built and maintained by Conor Bronsdon. I host the Chain of Thought podcast, which covers AI infrastructure, developer tools, and how practitioners actually use this stuff. I built this to pull YouTube's owner-side numbers into the agent workflows that run the show.

Companion tools:

  • gsc-mcp: Search Console — search performance, striking-distance keywords, sitemaps, URL inspection.

  • gws-mcp-server: Google Workspace — Gmail, Calendar, Drive, Sheets, Docs, Tasks.

  • op3-mcp: podcast analytics through OP3 — downloads, geography, apps, per-episode breakdowns.

  • Transistor MCP: Transistor.fm's official MCP server. Episodes, publishing, and analytics.

  • ai-tools-for-creators: a curated list of AI skills and MCP servers for people who ship ideas for a living.

More at conorbronsdon.com, chainofthought.show, GitHub, X, and LinkedIn.


Disclaimer

This is an independent personal project, not affiliated with, sponsored by, or endorsed by Google LLC or YouTube. All views expressed are my own.

License

MIT

Available Tools

9 tools
yt_audience_retentionAudience retention curve for one videoA
Read-onlyIdempotent

The retention curve for a single video: at each 1% slice of its runtime, what fraction of viewers were still watching (audienceWatchRatio) and how that compares with similar YouTube videos of similar length (relativeRetentionPerformance, 0-1 where 0.5 is median). This is where you find the drop-off — the intro that loses people, the mid-roll dip. Exactly one video ID: the API accepts a list but silently reports only the first, so this tool rejects lists rather than mislabel the result.

Inputs: end_date (required), video_id (required), start_date (required), metrics, +1 more.

yt_channel_infoIdentify the channel behind the credentialA
Read-onlyIdempotent

Show which YouTube channel this server's OAuth credential owns, with its public subscriber, view, and video counts. This is the entry point: every other tool reports on this channel and only this channel, so start here to confirm you are looking at the right account. Costs 1 YouTube Data API quota unit and needs the youtube.readonly scope; with only the analytics scope it reports the scopes it has instead.

Inputs: no named parameters.

yt_channel_overviewChannel performance over a date rangeA
Read-onlyIdempotent

Owner-side channel totals for a date range: views, watch time, average view duration and percentage, subscribers gained and lost, likes, comments, and shares. Pass group_by='day' or 'month' for a time series instead of a single total row. This is the 'how is the channel doing' tool — start with it, then drill into traffic sources or individual videos.

Inputs: end_date (required), start_date (required), metrics, group_by.

yt_episode_raceEpisode-over-episode race, normalised by ageA
Read-onlyIdempotent

The derived view: compare several videos on equal terms by re-indexing each one's daily numbers to days since its own publish date, so day 7 of a new episode sits next to day 7 of the last five. Raw totals cannot answer 'is this episode outperforming?' because an older video has simply had more days to accumulate; this removes that. Returns each video's cumulative curve by age plus a leaderboard at the oldest day all of them have reached, with each video's raw per-day API values returned alongside the running totals. Costs 1 YouTube Data API quota unit for publish dates (required — the normalisation is impossible without them) plus one Analytics query per video.

Inputs: video_ids (required), metric, as_of_date, window_days.

yt_geographyViews by countryA
Read-onlyIdempotent

Break views and watch time down by viewer country, ranked. Countries come back as ISO 3166-1 alpha-2 codes (US, GB, IN). Optionally scope to a single video. Useful for deciding where an audience actually is before making scheduling, language, or sponsorship calls.

Inputs: end_date (required), start_date (required), metrics, video_id, +1 more.

yt_playlist_performancePlaylist and podcast-series performanceA
Read-onlyIdempotent

How your playlists perform as playlists, not just as bags of videos. A YouTube podcast IS a playlist, so this is the series-level view: playlistStarts (how often someone began the series), viewsPerPlaylistStart (how many episodes they watched before leaving — the binge metric), averageTimeInPlaylist in seconds, playlistSaves, and playlist-scoped views and watch time. Group by 'playlist' to rank series, 'day' for a trend, or 'none' for a channel-wide total. Ranking by playlist requires a sort order, which this tool always sends.

Inputs: end_date (required), start_date (required), metrics, sort_by, +2 more.

yt_top_videosTop videos by a metricA
Read-onlyIdempotent

Rank the channel's videos over a date range by views, watch time, average view duration, or subscribers gained. Use this to find which videos to look at more closely — the IDs it returns feed yt_audience_retention, yt_traffic_sources, and yt_episode_race. max_results is capped at 200 by the API. Setting resolve_titles=true spends 1 YouTube Data API quota unit per 50 videos (default daily budget: 10,000 units); the Analytics query itself does not draw on that budget. Pass resolve_titles=false to return bare video IDs and spend none.

Inputs: end_date (required), start_date (required), metrics, sort_by, +2 more.

yt_traffic_sourcesWhere views came fromA
Read-onlyIdempotent

Break views and watch time down by how viewers arrived: YouTube search, suggested/related video, channel page, playlist, external sites, notifications, subscriptions feed, Shorts feed, end screens, and so on. The single most useful diagnostic for 'why did this take off' or 'why did it flatline'. Optionally scope it to one video. Source names come back as the API's own constants (YT_SEARCH, RELATED_VIDEO, EXT_URL, SHORTS, SUBSCRIBER, PLAYLIST, NOTIFICATION, END_SCREEN, NO_LINK_OTHER, and others).

Inputs: end_date (required), start_date (required), metrics, video_id.

yt_video_performancePerformance of specific videosA
Read-onlyIdempotent

Metrics for one or more named videos over a date range — the counterpart to yt_top_videos when you already know which videos you care about. Returns one row per video. Note that the window is a reporting window, not the video's lifetime: a range of the last 30 days shows the last 30 days of a video published two years ago. Setting resolve_titles=true spends 1 YouTube Data API quota unit per 50 videos (default daily budget: 10,000 units); the Analytics query itself does not draw on that budget. Pass resolve_titles=false to return bare video IDs and spend none.

Inputs: end_date (required), video_ids (required), start_date (required), metrics, +1 more.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.5/5.0

    Scored across 9 tools

    Disambiguation5/5

    Each tool targets a distinct analytical facet: channel identity, channel totals, traffic sources, video ranking, specific video metrics, retention curve, geography, playlist performance, and publish-relative comparison. Even overlapping tools (top_videos vs video_performance) are clearly differentiated as discovery vs known-video lookup, and episode_race explicitly reframes comparisons by publish age.

    Naming Consistency5/5

    All tool names follow the yt_ prefix with lowercase snake_case descriptive phrases (channel_info, traffic_sources, episode_race). The pattern is uniform and predictable, making it easy to infer a tool's purpose from its name alone.

    Tool Count5/5

    Nine tools cover the major dimensions of YouTube channel analytics without redundancy or bloat. Each tool addresses a meaningful analysis question, and the count is well within the ideal 3-15 range.

    Completeness4/5

    The surface covers core analytics: channel totals, traffic, top videos, per-video metrics, retention, geography, and playlists. A notable gap is demographic breakdowns (age/gender) and possibly device/reporting data, but these are less central and the provided tools cover the primary workflows.

    Maintenance

    ActivityMaintained
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers

    • F
      license
      Not graded
      quality
      D
      maintenance
      Provides AI assistants with comprehensive YouTube analytics and channel management capabilities, including channel performance, video analytics, audience insights, and content strategy tools.
      26 npm
      1
      -
    • A
      license
      A
      quality
      D
      maintenance
      Enables AI assistants to access YouTube organic analytics, including channel stats, video performance, watch time, and audience engagement, via the YouTube Data API v3 and Analytics API v2.
      6
      36 npm
      MIT