Skip to main content
Glama
README.md
# YouTube MCP Server

A [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server that exposes YouTube channel intelligence, video analysis, niche discovery, and content strategy tools to AI assistants such as Cursor, Claude Desktop, and other MCP-compatible clients.

Built for creator workflows: audit channels, benchmark videos, discover niches, score titles, and analyze transcripts — all through structured, agent-friendly JSON responses.

**Version:** `0.1.0` · **Node.js:** `>= 20` · **Transport:** stdio

---

## Table of Contents

- [Why This Exists](#why-this-exists)
- [Features](#features)
- [Architecture](#architecture)
- [Prerequisites](#prerequisites)
- [Quick Start](#quick-start)
- [MCP Client Setup](#mcp-client-setup)
- [Configuration](#configuration)
- [Tool Reference](#tool-reference)
- [Response Format](#response-format)
- [Quota & Caching](#quota--caching)
- [Transcript Modes](#transcript-modes)
- [Development](#development)
- [Testing](#testing)
- [Troubleshooting](#troubleshooting)
- [Project Structure](#project-structure)
- [Roadmap](#roadmap)
- [Security](#security)

---

## Why This Exists

YouTube creator research usually means juggling the Data API, spreadsheets, and ad-hoc scripts. This server wraps that work into a consistent MCP tool surface so an AI agent can:

- Resolve messy inputs (`@handle`, video URLs, channel IDs) into canonical records
- Fetch channel and video metadata with quota-aware caching
- Run opinionated analysis (channel audits, niche scoring, title packaging)
- Return predictable JSON envelopes that agents can reason over reliably

Every tool response includes `data`, `summary`, `sources`, and `warnings` so downstream workflows stay auditable.

---

## Features

| Category | Capabilities |
|----------|--------------|
| **Operations** | Health checks, auth status, quota tracking, cache statistics |
| **Channels** | Resolve identifiers, fetch profiles, list recent uploads |
| **Videos** | Details, batch lookup, search, performance snapshots, thumbnails |
| **Strategy** | Full channel audits, niche opportunity ranking |
| **Content** | Transcript analysis (user-provided text), title scoring |

### v0.1 Tool Inventory (17 tools)

<details>
<summary><strong>Operational (4)</strong></summary>

| Tool | Description |
|------|-------------|
| `youtube.healthcheck` | Server readiness, API reachability, schema version |
| `youtube.auth.status` | API key and OAuth configuration status |
| `youtube.quota.status` | Daily quota usage by endpoint |
| `youtube.cache.status` | Cache size, hit rate, stale entries |

</details>

<details>
<summary><strong>Channel & Video (8)</strong></summary>

| Tool | Description |
|------|-------------|
| `youtube.channel.resolve` | Resolve URL, handle, ID, or video URL → channel |
| `youtube.channel.get_profile` | Title, stats, thumbnails, branding metadata |
| `youtube.channel.get_uploads` | Recent upload IDs via uploads playlist |
| `youtube.video.get_details` | Metadata, stats, duration, thumbnails |
| `youtube.video.batch_get_details` | Batch lookup (up to 50 videos) |
| `youtube.video.search` | Keyword search with filters |
| `youtube.video.performance_snapshot` | Views/day, engagement rate, packaging metrics |
| `youtube.thumbnail.get` | All thumbnail variants and dimensions |

</details>

<details>
<summary><strong>Strategy & Analysis (5)</strong></summary>

| Tool | Description |
|------|-------------|
| `youtube.strategy.channel_audit` | Upload cadence, outliers, title patterns |
| `youtube.niche.find` | Rank niche opportunities from seed topics |
| `youtube.transcript.get` | Transcript retrieval (provided text mode) |
| `youtube.transcript.analyze` | Hook, structure, CTA, repurpose signals |
| `youtube.packaging.analyze_title` | Title clarity, curiosity, length scoring |

</details>

---

## Architecture

```mermaid
flowchart TB
    subgraph Client["MCP Client"]
        Cursor["Cursor / Claude / Inspector"]
    end

    subgraph Server["youtube-mcp-server"]
        MCP["MCP Server (stdio)"]
        Registry["Tool Registry"]
        Analyzer["Channel Analyzer"]
        MCP --> Registry
        Registry --> Analyzer
    end

    subgraph Services["YouTube Layer"]
        ChannelSvc["Channel Service"]
        VideoSvc["Video Service"]
        Client_YT["YouTube Client"]
        Registry --> ChannelSvc
        Registry --> VideoSvc
        ChannelSvc --> Client_YT
        VideoSvc --> Client_YT
        Analyzer --> ChannelSvc
        Analyzer --> VideoSvc
    end

    subgraph Storage["Persistence"]
        Cache["SQLite API Cache"]
        Quota["SQLite Quota Tracker"]
        Client_YT --> Cache
        Client_YT --> Quota
    end

    subgraph External["External"]
        API["YouTube Data API v3"]
        Client_YT --> API
    end

    Cursor <-->|stdio| MCP
```

**Design principles**

- **Stdio transport** — runs as a subprocess; no HTTP server to deploy
- **Zod validation** — strict input schemas on every tool call
- **SQLite persistence** — response cache and quota ledger share one database file
- **Quota guardrails** — pre-flight checks before each API call; configurable daily budget
- **Structured envelopes** — uniform `{ data, summary, sources, warnings }` responses

---

## Prerequisites

1. **Node.js 20+** — [nodejs.org](https://nodejs.org/)
2. **YouTube Data API v3 key** — from [Google Cloud Console](https://console.cloud.google.com/)

### Obtaining a YouTube API Key

1. Create or select a Google Cloud project
2. Enable **YouTube Data API v3** under *APIs & Services → Library*
3. Go to *APIs & Services → Credentials → Create Credentials → API Key*
4. Restrict the key to YouTube Data API v3 (recommended for production)
5. Copy the key into your environment (see [Configuration](#configuration))

> **Note:** Default Google Cloud quota is **10,000 units/day**. This server defaults to a **9,000 unit** soft limit to leave headroom.

---

## Quick Start

```bash
# Clone and install
git clone <your-repo-url> youtube-mcp-server
cd youtube-mcp-server
npm install

# Configure credentials
cp .env.example .env
# Edit .env and set YOUTUBE_API_KEY=your-key-here

# Build and verify
npm run build
npm test
```

Verify the server with the MCP Inspector:

```bash
npx @modelcontextprotocol/inspector node dist/index.js
```

Then invoke `youtube.healthcheck` and `youtube.channel.resolve` with:

```json
{ "input": "@mkbhd" }
```

---

## MCP Client Setup

The server communicates over **stdio**. Point your MCP client at the built entry point (`dist/index.js`) or the dev runner (`tsx src/index.ts`).

### Cursor

Add to `~/.cursor/mcp.json` (Windows: `%USERPROFILE%\.cursor\mcp.json`):

**Production (compiled)**

```json
{
  "mcpServers": {
    "youtube": {
      "command": "node",
      "args": ["/absolute/path/to/youtube-mcp-server/dist/index.js"],
      "env": {
        "YOUTUBE_API_KEY": "your-api-key-here"
      }
    }
  }
}
```

**Development (hot reload via tsx)**

```json
{
  "mcpServers": {
    "youtube": {
      "command": "npx",
      "args": ["tsx", "/absolute/path/to/youtube-mcp-server/src/index.ts"],
      "env": {
        "YOUTUBE_API_KEY": "your-api-key-here"
      }
    }
  }
}
```

### Claude Desktop

Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or the equivalent path on your OS:

```json
{
  "mcpServers": {
    "youtube": {
      "command": "node",
      "args": ["/absolute/path/to/youtube-mcp-server/dist/index.js"],
      "env": {
        "YOUTUBE_API_KEY": "your-api-key-here"
      }
    }
  }
}
```

### Using a `.env` file

The server auto-loads `.env` from the project root when present. If your MCP config launches the server from the project directory, you can omit inline `env` keys and rely on the file instead:

```bash
YOUTUBE_API_KEY=your-api-key-here
CACHE_DB_PATH=./data/cache.db
```

> Environment variables set in the MCP client config take precedence over `.env` values already in `process.env`; unset keys fall through to `.env`.

---

## Configuration

| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `YOUTUBE_API_KEY` | **Yes** | — | YouTube Data API v3 key |
| `CACHE_DB_PATH` | No | `./data/cache.db` | SQLite database for cache + quota |
| `MAX_DAILY_QUOTA_UNITS` | No | `9000` | Soft daily quota budget |
| `CACHE_TTL_CHANNEL_HOURS` | No | `24` | TTL for channel/profile cache |
| `CACHE_TTL_VIDEO_HOURS` | No | `12` | TTL for video detail cache |
| `CACHE_TTL_SEARCH_HOURS` | No | `6` | TTL for search result cache |
| `TRANSCRIPT_MODE` | No | `provided_text` | Comma-separated transcript modes |
| `PUBLIC_TRANSCRIPT_ADAPTER_ENABLED` | No | `false` | Enable public transcript adapter |
| `GOOGLE_CLIENT_ID` | No | — | OAuth client ID (future caption support) |
| `GOOGLE_CLIENT_SECRET` | No | — | OAuth client secret |
| `GOOGLE_REDIRECT_URI` | No | — | OAuth redirect URI |

Copy `.env.example` as a starting point:

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

---

## Tool Reference

All tools accept JSON arguments and return a [structured response](#response-format). Use `forceRefresh: true` to bypass cache when you need live data (consumes quota).

### Operational

```json
// youtube.healthcheck
{}

// youtube.quota.status
{}
```

### Channel resolution & profiles

```json
// youtube.channel.resolve
{ "input": "@mkbhd" }
{ "input": "https://www.youtube.com/watch?v=dQw4w9WgXcQ" }

// youtube.channel.get_profile
{ "channel": "@mkbhd", "forceRefresh": false }

// youtube.channel.get_uploads
{ "channel": "UC...", "maxResults": 25, "pageToken": null }
```

**Accepted channel identifiers:** `@handle`, channel URL, `UC...` channel ID, custom URL, or a video URL (resolved to its channel).

### Video lookup & search

```json
// youtube.video.get_details
{ "video": "dQw4w9WgXcQ", "includeTags": true }

// youtube.video.batch_get_details
{ "videos": ["id1", "id2", "https://youtu.be/id3"], "includeTags": false }

// youtube.video.search
{
  "query": "home gym setup",
  "maxResults": 10,
  "order": "viewCount",
  "type": "video",
  "regionCode": "US",
  "videoDuration": "medium",
  "recency": "pastMonth"
}

// youtube.video.performance_snapshot
{ "video": "dQw4w9WgXcQ" }

// youtube.thumbnail.get
{ "video": "dQw4w9WgXcQ" }
```

**Search `order` values:** `relevance`, `date`, `viewCount`, `rating`

**Search `recency` values:** `any`, `pastHour`, `pastDay`, `pastWeek`, `pastMonth`, `pastQuarter`, `pastYear`

### Strategy & content analysis

```json
// youtube.strategy.channel_audit
{ "channel": "@mkbhd", "maxVideos": 25 }

// youtube.niche.find
{
  "seedTopics": ["minimalist desk setup", "standing desk review"],
  "regionCode": "US",
  "maxResults": 10
}

// youtube.transcript.get (provided text)
{
  "mode": "provided_text",
  "transcriptText": "Welcome back to the channel...",
  "language": "en"
}

// youtube.transcript.analyze
{
  "transcriptText": "In this video we cover...",
  "analysisTypes": ["hook", "structure", "cta", "repurpose"]
}

// youtube.packaging.analyze_title
{ "title": "I Tried Every Standing Desk Under $300" }
```

### Channel audit output highlights

`youtube.strategy.channel_audit` returns:

- **Upload cadence** — videos/week, consistency score, average gap between uploads
- **Performance** — median views, average views/day, engagement rate, outlier count
- **Top videos** — highest-performing uploads with engagement metrics
- **Outlier videos** — uploads exceeding 2× channel median views
- **Title patterns** — average length, common words, detected formulas
- **Thumbnail availability** — coverage across analyzed uploads

### Niche scoring

`youtube.niche.find` searches each seed topic, samples top results, and scores opportunities using demand and competition proxies. Results are ranked by `overallScore`.

---

## Response Format

Successful tool calls return JSON text with this envelope:

```json
{
  "data": { },
  "summary": "Human-readable one-liner for the agent",
  "sources": [
    {
      "type": "youtube_api",
      "endpoint": "channels.list",
      "url": "https://www.youtube.com/@mkbhd",
      "timestamp": "2026-07-07T12:00:00.000Z"
    }
  ],
  "warnings": []
}
```

Errors return a separate JSON object with `isError: true`:

```json
{
  "error": {
    "code": "QUOTA_EXCEEDED",
    "message": "Daily quota limit reached (9000/9000 units used)",
    "retryable": true
  }
}
```

**Error codes**

| Code | Retryable | Meaning |
|------|-----------|---------|
| `QUOTA_EXCEEDED` | Yes | Daily soft limit or Google quota hit |
| `TRANSCRIPT_UNAVAILABLE` | No | Requested transcript mode not available |
| `UNKNOWN_TOOL` | No | Tool name not registered |
| `INTERNAL_ERROR` | No | Unexpected server error |

---

## Quota & Caching

### Quota costs (estimated units per call)

| Endpoint | Cost |
|----------|------|
| `channels.list` | 1 |
| `videos.list` | 1 |
| `playlistItems.list` | 1 |
| `playlists.list` | 1 |
| `search.list` | **100** |
| `captions.list` | 50 |
| `commentThreads.list` | 1 |

The quota tracker records usage in SQLite and enforces `MAX_DAILY_QUOTA_UNITS` before each request. Check status anytime:

```json
// youtube.quota.status →
{
  "dailyLimit": 9000,
  "usedToday": 342,
  "remaining": 8658,
  "byEndpoint": { "search.list": 300, "videos.list": 42 },
  "date": "2026-07-07"
}
```

### Caching behavior

- Responses are keyed by endpoint + normalized request parameters (SHA-256 hash)
- TTLs are configurable per resource type (channel, video, search)
- Stale entries are returned as cache misses and refreshed on next call
- `forceRefresh: true` skips cache reads (still records quota on API hit)

**Tips for quota efficiency**

1. Prefer `youtube.video.batch_get_details` over repeated `get_details` calls
2. Use `youtube.channel.get_profile` before re-fetching the same channel
3. Treat `youtube.video.search` as expensive (~100 units each)
4. Run `youtube.niche.find` with fewer seed topics during development
5. Monitor with `youtube.quota.status` and `youtube.cache.status`

---

## Transcript Modes

Official YouTube caption download requires OAuth and (for most captions) video owner permissions. v0.1 supports:

| Mode | Status | Description |
|------|--------|-------------|
| `provided_text` | **Supported** | User pastes transcript text for analysis |
| `owner_oauth` | Planned | OAuth-based owner caption access |
| `public_adapter` | Disabled | Third-party public transcript adapter |
| `speech_to_text` | Planned | Audio → text pipeline |

Configure enabled modes via `TRANSCRIPT_MODE` (comma-separated). Every transcript response includes provenance metadata.

**Example workflow**

1. Copy transcript text manually (or from your own pipeline)
2. Call `youtube.transcript.get` with `mode: "provided_text"`
3. Pass the text to `youtube.transcript.analyze` for hook/structure/CTA insights

---

## Development

```bash
# Run server directly (stdio — intended for MCP clients)
npm run dev

# Type-check
npm run typecheck

# Build for production
npm run build
npm start
```

### NPM scripts

| Script | Description |
|--------|-------------|
| `npm run dev` | Start via `tsx` (no build step) |
| `npm run build` | Compile TypeScript → `dist/` |
| `npm start` | Run compiled `dist/index.js` |
| `npm test` | Run Vitest unit tests |
| `npm run typecheck` | `tsc --noEmit` |

### Tech stack

- **Runtime:** Node.js 20+, ESM (`"type": "module"`)
- **MCP SDK:** `@modelcontextprotocol/sdk`
- **Validation:** Zod
- **Storage:** better-sqlite3
- **Testing:** Vitest

---

## Testing

```bash
npm test
```

Unit tests cover identifier parsing (`@handle`, URLs, channel IDs), duration/engagement utilities, title scoring, and transcript analysis heuristics.

---

## Troubleshooting

| Symptom | Likely cause | Fix |
|---------|--------------|-----|
| `YOUTUBE_API_KEY is required` | Missing API key | Set in `.env` or MCP `env` block |
| `QUOTA_EXCEEDED` | Daily limit hit | Wait for reset (midnight Pacific) or raise `MAX_DAILY_QUOTA_UNITS` |
| `YouTube API error (403)` | API not enabled or key restricted | Enable YouTube Data API v3; check key restrictions |
| Server starts but tools fail | Wrong working directory | Use absolute paths in MCP config `args` |
| Empty search results | Overly narrow filters | Relax `recency`, `videoDuration`, or `regionCode` |
| `TRANSCRIPT_UNAVAILABLE` | Unsupported mode | Use `provided_text` with `transcriptText` |
| Cache shows stale entries | Normal TTL expiry | Stale entries refresh on next miss; or use `forceRefresh` |

**Debug with MCP Inspector**

```bash
npx @modelcontextprotocol/inspector node dist/index.js
```

Inspect raw tool inputs/outputs, list registered tools, and verify API connectivity without an IDE.

---

## Project Structure

```
youtube-mcp-server/
├── src/
│   ├── index.ts              # Entry point, .env loader
│   ├── server/
│   │   ├── mcpServer.ts      # MCP server + stdio transport
│   │   ├── toolRegistry.ts   # Tool handlers + definitions
│   │   └── schemas.ts        # Zod input schemas
│   ├── youtube/
│   │   ├── youtubeClient.ts  # API client, cache, quota integration
│   │   ├── channelService.ts # Channel resolve, profile, uploads
│   │   ├── videoService.ts   # Video details, search, snapshots
│   │   └── quotaTracker.ts   # Daily quota ledger
│   ├── analysis/
│   │   └── channelAnalyzer.ts # Audits, niche scoring, title/transcript analysis
│   ├── storage/
│   │   └── cache.ts          # SQLite response cache
│   ├── config/
│   │   ├── env.ts            # Environment validation
│   │   └── defaults.ts       # Quota costs, schema version
│   ├── utils/
│   │   ├── ids.ts            # URL/ID parsing
│   │   ├── duration.ts       # ISO duration, engagement math
│   │   └── response.ts       # Response envelope helpers
│   └── tests/
│       └── unit/             # Vitest unit tests
├── .env.example
├── package.json
├── tsconfig.json
└── vitest.config.ts
```

---

## Roadmap

v0.1 ships 17 tools. A broader roadmap (53+ tools) is documented separately, including:

- OAuth-based owner caption download
- Thumbnail vision analysis
- Competitor comparison reports
- Export and reporting utilities

See the parent [YouTube MCP Server Build Plan](../YOUTUBE_MCP_SERVER_PLAN.md) for the full phased rollout.

---

## Security

- **Never commit** `.env`, API keys, or `*.db` files — they are gitignored
- **Restrict your API key** to YouTube Data API v3 and (optionally) specific IPs
- **Prefer MCP `env` injection** or OS-level secrets over hardcoding keys in config files shared via git
- **Quota limits** are enforced server-side, but Google Cloud quotas are the ultimate ceiling
- OAuth credentials (`GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`) are optional and only needed for future caption features

---

<p align="center">
  <sub>Built for AI-assisted YouTube creator workflows · MCP stdio server · YouTube Data API v3</sub>
</p>

TDQS

A3.5/5.0

Scored across 17 tools

Disambiguation5/5

Each tool has a clearly distinct purpose, organized by category prefixes (e.g., youtube.channel, youtube.video). No two tools overlap in functionality; an agent can easily differentiate them.

Naming Consistency5/5

All tool names follow a consistent pattern: lowercase with dot-separated categories and underscores (e.g., youtube.channel.get_profile, youtube.video.get_details). No mixing of conventions.

Tool Count5/5

17 tools cover a comprehensive range of YouTube data and analysis operations (channels, videos, transcripts, niche analysis, etc.) without being excessive. The count is well-scoped for the server's purpose.

Completeness5/5

The tool set covers core YouTube information retrieval and analysis needs: channel details, video details, transcripts, thumbnails, search, niche analysis, and channel audit. Missing CRUD operations are outside the server's analytical scope.

Maintenance

ActivityStale
ResponsivenessNo issues