Skip to main content
Glama

consolidado — MCP server de YouTube

Um servidor MCP (Python 3.12) que junta três servidores open-source de YouTube em um só catálogo de 15 tools e acrescenta a camada que faltava: descobrir canais emergentes por filtros compostos — idade do canal, pico de views, duração média, país e faixa de inscritos — com cota medida e shortfall honesto.

Responde a esta pergunta, que nenhum dos três originais responde sozinho:

Quais canais publicaram pela primeira vez há menos de 30 dias, já têm um vídeo acima de 100 mil views, falam com público dos EUA e têm duração média entre 8 e 30 minutos?


Instalação em 3 comandos

git clone <este-projeto> C:\caminho\consolidado
cd C:\caminho\consolidado
uv sync --python 3.12

Depois registre no cliente MCP (bloco pronto abaixo) e reinicie o app. Tempo real de setup em máquina limpa: ~2 min de download + 10 s de configuração.

Related MCP server: YouTube MCP Server Enhanced

1. Pré-requisitos

item

versão

obrigatório

por quê

Python

3.12+

sim

requires-python >=3.12 no pyproject.toml

uv

0.9+

sim

resolve e tranca as dependências (uv.lock)

Chave da YouTube Data API v3

—

sim, para as tools de API

100 unidades por search.list

ffmpeg

—

não

os tools de frame capture do tube-bridge não foram portados

Node.js

—

não

o servidor é Python puro

Conferir se ficou tudo no lugar:

uv run python -c "import mcp, googleapiclient, yt_dlp, feedparser, youtube_transcript_api; print('ok')"

2. Variáveis de ambiente

Obrigatória para as tools de Data API:

$env:YOUTUBE_API_KEYS = "AIza...key1,AIza...key2,AIza...key3"   # pool CSV, rotaciona em quotaExceeded

Aceita também key única:

$env:YOUTUBE_API_KEY = "AIza...key1"

Opcionais: nenhuma. O servidor não lê cache em disco, proxy ou timeout por variável de ambiente — só as duas acima (auth.py:128-129). Sem key, as tools de API devolvem {"success": false, "error": "No YouTube API key configured..."} e as de cota zero (youtube_get_channel_videos, get_channel_rss_videos, youtube_get_transcript) continuam funcionando.

3. Claude Desktop

Edite %APPDATA%\Claude\claude_desktop_config.json (feche o app antes; ele sobrescreve o arquivo ao sair):

{
  "mcpServers": {
    "yt-consolidado": {
      "command": "C:\\caminho\\consolidado\\.venv\\Scripts\\python.exe",
      "args": ["-m", "consolidado.server"],
      "env": {
        "YOUTUBE_API_KEYS": "AIza...key1,AIza...key2"
      }
    }
  }
}

4. Cursor

Edite %USERPROFILE%\.cursor\mcp.json:

{
  "mcpServers": {
    "yt-consolidado": {
      "command": "C:\\caminho\\consolidado\\.venv\\Scripts\\python.exe",
      "args": ["-m", "consolidado.server"],
      "env": {
        "YOUTUBE_API_KEYS": "AIza...key1,AIza...key2"
      }
    }
  }
}

Transporte HTTP (para clientes remotos):

uv run python -m consolidado.server --transport streamable-http --host 127.0.0.1 --port 8000
uv run python -m consolidado.server --print-tools      # catálogo das 15 tools em JSON

5. Primeiro uso — prompt e resposta esperada

Exemplo A — busca simples (custo 103):

"Busque 3 vídeos sobre 'personal finance' publicados nos últimos 30 dias, ordene por data e devolva título, canal, views e duração."

O agente chama search_videos (100) e depois youtube_get_video_info por id (1 cada). Resposta real obtida (aqui resumida):

título

canal

views

duração

The rich never budget — New Personal Finance drops daily…

yodi montali

2

1:09

Forgot a paycheck? Your state may have it.

Deep Frame

0

0:31

I hate his smile — @Caleb Hammer

Seeking Finance clips

122

1:14

Exemplo B — o produto, execução original (custo 193; anterior à correção da base de idade):

"Quero 10 canais que estejam bombando agora, primeira publicação há menos de 30 dias, algum vídeo com mais de 100 mil views, público norte-americano, duração média entre 8 e 30 minutos."

{
  "success": true,
  "query": "personal finance",
  "n_requested": 10,
  "n_returned": 0,
  "channels": [],
  "near_misses": [
    { "channel_title": "Ben Talks Finance", "max_views": 28126, "reason": "max_views below 100000" },
    { "channel_title": "Deep Frame", "max_views": 1615, "reason": "max_views below 100000" }
  ],
  "rejected_counts": { "not_new": 10, "low_views": 15, "duration": 0, "verify_error": 0 },
  "quota_total": 193,
  "quota_breakdown": { "discovery": 100, "enrichment": 1, "verification": 92 },
  "shortfalls": "Returned 0 of 10 requested. 36 unique channels found in discovery; 26 survived country/subscriber filters; 10 rejected: first publication older than 30 days; 15 rejected: no video above 100000 views; 0 rejected: average duration outside 8.0-30.0 min; 1 candidates skipped by max_candidates_checked=25. Filters were not relaxed automatically - widen them deliberately if you want more."
}

Zero não é bug. O near_misses mostra o teto real da população naquele nicho (28 mil views, não 100 mil). O produto devolve o que os filtros permitem e diz exatamente quem caiu em qual corte — não relaxa nada sozinho.

Exemplo C — base de idade corrigida (execução ao vivo, custo 210):

max_channel_age_days passou a medir a data do primeiro vídeo publicado, nunca a data de criação da conta. Cada candidato tem a playlist de uploads enumerada (até first_video_pages páginas) para provar quando começou; quem não prova é rejeitado como age_unproven em vez de estimado pela conta.

{
  "success": true,
  "query": "world war 2 documentary",
  "n_requested": 15,
  "n_returned": 0,
  "near_misses": [
    { "channel_title": "Why Though?", "first_video_published_at": "2026-10-14T23:00:24Z",
      "max_views": 240116, "avg_duration_minutes": 2.3, "reason": "avg duration outside 8.0-30.0 min" },
    { "channel_title": "WW2 HIDDEN CHAPTERS", "first_video_published_at": "2026-10-23T18:45:16Z",
      "max_views": 4012, "avg_duration_minutes": null, "reason": "max_views below 100000" }
  ],
  "rejected_counts": { "not_new": 6, "low_views": 14, "duration": 1, "verify_error": 0, "age_unproven": 4 },
  "quota_total": 210,
  "quota_breakdown": { "discovery": 100, "enrichment": 1, "verification": 109 },
  "shortfalls": "Returned 0 of 15 requested. 48 unique channels found in discovery; 28 survived country/subscriber filters; 6 rejected: first publication older than 30 days; 14 rejected: no video above 100000 views; 1 rejected: average duration outside 8.0-30.0 min; 4 rejected: first video not proven within 3 upload pages; 3 candidates skipped by max_candidates_checked=25. Filters were not relaxed automatically - widen them deliberately if you want more."
}

Artefato bruto: prova-de-tela/raw/live-age-basis.json.

6. Tools e custo de cota

Unidades oficiais da YouTube Data API v3 (auth.py::ENDPOINT_COST).

tool

origem

camada

cota por chamada

o que a torna cara

search_videos

youtube-mcp

API → yt-dlp

100

search.list é o endpoint mais caro da API

youtube_search

tube-bridge

API → yt-dlp

100

idem; sem key cai para yt-dlp (0)

search_channels

youtube-mcp

API

101

search.list (100) + channels.list (1)

get_channel_details

youtube-mcp

API

1

um único channels.list

find_breakout_creators

youtube-mcp

API

101 + 1–3/canal

search.list + channels.list em lote + enumeração do primeiro vídeo por candidato

channels_getChannel

youtube-mcp-server (TS)

API

1 (ou 101)

101 quando resolve o último upload via search.list; include_latest_upload=false derruba para 1

get_channel_rss_videos

youtube-mcp

RSS

0

Atom público, sem key

youtube_get_video_info

tube-bridge

API → yt-dlp

1

videos.list; sem key, yt-dlp (0)

youtube_get_channel_videos

tube-bridge

yt-dlp → RSS

0

scraping, sem key

youtube_get_transcript

tube-bridge

timedtext público

0

sem key (pode ser bloqueado por IP — ver limitações)

get_first_published_date

nova

API

1 + 1/50

channels.list + playlistItems.list paginado (1 por página de 50)

max_views_in_channel

nova

API

1 + páginas + lotes

playlistItems.list por página + videos.list em lote de 50

avg_duration

nova

API

2

resolução do canal (1) + videos.list (1)

find_emerging_channels

nova (produto)

API

100/página + 1/50 + 2–6/canal

100 fixos na descoberta; 193 medidos antes da correção de idade, 210 depois (25 candidatos, first_video_pages=3)

get_quota_status

nova

—

0

lê o QuotaTracker da sessão

Regras práticas:

  • Duas buscas (search_videos + youtube_search) custam 200 unidades. search.list é 100 fixo, mesmo com max_results=1.

  • dry_run=true no orquestrador custa 100 e mostra os candidatos antes de gastar verificação.

  • get_quota_status() devolve o extrato da sessão: unidades por tool, por endpoint e keys restantes.

7. find_emerging_channels — a tool do produto

find_emerging_channels(
    query,                    # nicho
    n=10,                     # canais desejados (não é garantia de retorno)
    max_channel_age_days=30,  # primeira publicação há menos de N dias
    min_video_views=100000,   # algum vídeo acima de X views
    region_code="US",
    relevance_language="en",
    min_avg_duration_min=8.0,
    max_avg_duration_min=30.0,
    subscriber_min=0,
    subscriber_max=100000,
    dry_run=False,            # True para na descoberta (100 unidades)
    discovery_pages=1,        # 100 unidades por página de 50 vídeos
    max_candidates_checked=25,
    sample_size=10,           # vídeos lidos por canal na verificação
    small_channel_video_cap=100,  # compatibilidade; a idade não usa mais este cap
    first_video_pages=3,      # páginas de uploads lidas por canal para provar o primeiro vídeo
)

Cinco etapas: descoberta (search.list), enriquecimento (channels.list em lote de 50), verificação dura (primeiro vídeo publicado, pico de views, duração média), ranking e saída.

Base da idade. O corte max_channel_age_days mede a data do primeiro vídeo publicado, nunca a data de criação da conta. Um canal aberto há anos que começou a postar semana passada passa — desde que o primeiro vídeo seja provado dentro de first_video_pages páginas de uploads. Sem prova, o canal sai como age_unproven; a conta não é usada como estimativa.

A mesma base vale para find_breakout_creators: channel_age_months vem do primeiro vídeo. É uma quebra deliberada de paridade com o find_breakout_growth_channels do youtube-mcp, que envelhecia pelo cadastro; a saída mantém created_at (conta) e acrescenta first_video_published_at.

Três garantias:

  1. Nada é relaxado automaticamente. Se voltarem menos que n, o campo shortfalls explica quem caiu em qual filtro e o resultado sai como está.

  2. near_misses está sempre no output. Os canais que passaram a idade e falharam em views/duração vêm com max_views e avg_duration_minutes, ordenados por pico — é o teto real da população antes de você afrouxar o corte.

  3. dry_run=True para na etapa 1. 100 unidades para inspecionar os candidatos antes de gastar verificação.

8. Limitações conhecidas

limitação

efeito

contorno

Transcript bloqueado por IP

youtube_get_transcript devolve erro in-band quando o YouTube bloqueia o IP (comum em cloud/hosting/VPN). Não há dado de transcrição nesse caso.

rodar de IP residencial; a paridade de dados dessa tool ficou não provada na FASE 6 por esse motivo

Erro in-band, não exceção

falha de upstream em youtube_get_video_info, youtube_get_channel_videos, youtube_get_transcript e channels_getChannel vem como {"error": "..."} com resultado vazio — o MCP não marca isError

cheque error/success no payload, não só o status do protocolo

Shortfall é normal em nicho estreito

n_returned < n quando a população não tem canais que passem em todos os filtros

leia near_misses e rejected_counts, afrouxe o filtro que você escolher

Contadores ao vivo

view_count muda entre duas chamadas

compare com o mesmo instante, não entre execuções

Sem cache entre processos

cada chamada paga cota de novo (o tube-bridge original tinha cache SQLite)

use dry_run e get_quota_status para controlar

search.list é caro

100 unidades por chamada, max_results não muda o preço

prefira search_videos uma vez e enriqueça com youtube_get_video_info (1 cada)

Chamadas serializadas

o cliente da API roda uma requisição por vez (lock process-wide); em paralelo elas enfileiram em vez de estourar SSL

comportamento intencional — contra SSLError/timeout; um search que falha 3× segura a fila ~6 s

Retry automático

erro de rede (SSL, timeout, conexão) é re-tentado 3× com espera de 2 s e 4 s antes de virar erro

não há retry para erro de cota: cota esgotada rotaciona de key, não repete

Timeout de socket 30 s

definido em auth.py (httplib2.Http(timeout=30)); sem ele o cliente esperava o timeout do sistema operacional

acima disso a chamada já é lenta demais para um cliente MCP

Verificação em lotes de 10

find_emerging_channels para de verificar assim que bate n; skipped_after_goal diz quantos candidatos ficaram sem verificar

se você quer o censo completo, chame com n alto e near_misses traz o resto

9. Testes

uv run pytest tests\ -v

31 testes, nenhum toca a rede: parser ISO 8601, escolha de proxy, corte por data, média e arredondamento, cota por chamada, staging do orquestrador, shortfall sem preenchimento artificial, ranking e cap de candidatos. O FakeClient cobra as mesmas unidades oficiais.

10. Estrutura

src/consolidado/
  auth.py            pool de keys + QuotaTracker + ENDPOINT_COST
  youtube_client.py  wrapper da API v3, rotação em 403/quota
  ytdlp_client.py    camada 3 (0 cota)
  rss_client.py      camada 4 (0 cota)
  server.py          registro das 15 tools, stdio/sse/http
  tools/
    search.py        search_videos, youtube_search
    channels.py      search_channels, get_channel_details, find_breakout_creators,
                     channels_getChannel, get_channel_rss_videos
    videos.py        youtube_get_video_info, youtube_get_channel_videos,
                     youtube_get_transcript
    custom.py        get_first_published_date, max_views_in_channel, avg_duration
    orchestrator.py  find_emerging_channels
tests/
  conftest.py        FakeClient com cota real
  test_custom.py
  test_orchestrator.py

11. Origem e paridade

10 tools portadas de youtube-mcp (5), tube-bridge (4) e youtube-mcp-server (1), com paridade semântica verificada caso a caso em 06-paridade.md (30 pares, 0 divergência de dado essencial, 8 desvios documentados). 5 tools novas: get_first_published_date, max_views_in_channel, avg_duration, find_emerging_channels, get_quota_status.

Decisões de projeto, desvios de spec e evidências brutas: ../../05-reconstruir.md, ../../06-paridade.md e ../../07-checklist-final.md.

Available Tools

15 tools
avg_durationB

Mean duration of the last N uploads. New tool.

Layer: Data API. Quota: 2 units for N ≤ 50 (1 playlist page + 1 videos batch).

Inputs: channel_id (required), sample_size. Returns: object

channels_getChannelB

Raw channel + normalizedMetadata — port of youtube-mcp-server (zubeid).

Layer: Data API. Quota: 1 unit; **+100 units** when ``include_latest_upload``
is true (the original resolves the newest upload with one search.list).
Set it to false when you only need country/joinedAt/emailsFound.

Inputs: channelId (required), include_latest_upload. Returns: object

find_breakout_creatorsC

New channels with subscriber velocity — port of youtube-mcp find_breakout_growth_channels.

Layer: Data API. Quota: 101 units.

Inputs: niche (required), max_results, region_code, max_subscribers, +2 more. Returns: object

find_emerging_channelsB

Emerging channels by composite filters — the product tool.

Layer: Data API. Quota: 100 per discovery page + 1 per 50 channels enriched
+ 3–5 per channel verified (~130–450 for a default run).
``small_channel_video_cap`` also enumerates channels with few uploads, so an
old channel that only started publishing last week is still caught.
Shortfalls are reported, never padded: filters are not relaxed automatically.

Inputs: query (required), n, dry_run, region_code, +11 more. Returns: object

get_channel_detailsB

Channel metadata by id/@handle/username — port of youtube-mcp.

Layer: Data API. Quota: 1 unit. Includes ``uploads_playlist_id``.

Inputs: raw, channel_id, for_handle, for_username. Returns: object

get_channel_rss_videosB

Latest uploads from the Atom feed — port of youtube-mcp get_channel_rss_videos.

Layer 4 (RSS). Quota: 0 units, no key. Feed exposes at most 15 entries.

Inputs: channel_id (required), max_results. Returns: object

get_first_published_dateC

Oldest publication date of a channel. Did not exist in any original.

Layer: Data API. Quota: 1 unit (channel creation proxy), plus 1 unit per 50
uploads when enumeration runs — when the channel is younger than
``enumerate_if_younger_than_days`` or has at most
``enumerate_if_video_count_under`` uploads (0 = never enumerate on size).

Inputs: channel_id (required), max_pages, enumerate_if_video_count_under, enumerate_if_younger_than_days. Returns: object

get_quota_statusA

Session quota ledger: units spent per tool and per endpoint, layers used.

No API call, 0 units. The Data API does not expose remaining quota, so this
is a session-local tally against the 10,000/day free tier per key.

Inputs: no named parameters. Returns: object

max_views_in_channelA

Highest view count among uploads since since_date. New tool.

Layer: Data API. Quota: 1 + 1 per 50 uploads scanned + 1 per 50 videos in
the window (``max_pages`` default caps at 500 uploads).

Inputs: channel_id (required), since_date (required), max_pages. Returns: object

search_channelsC

Search channels enriched with stats — port of youtube-mcp search_channels.

Layer: Data API. Quota: 101 units (100 search + 1 channels batch).

Inputs: query (required), order, max_results, region_code. Returns: object

search_videosB

Search videos/channels/playlists — port of youtube-mcp search_videos.

Layer: YouTube Data API (key required). Quota: 100 units.
Returns {success, query, total_results, next_page_token, results[]}.

Inputs: query (required), raw, order, max_results, +3 more. Returns: object

youtube_get_channel_videosB

Recent uploads via yt-dlp — port of tube-bridge youtube_get_channel_videos.

Layer 3 (0 units, no key). Falls back to Layer 4 (RSS) if yt-dlp is 403'd
and the argument is a ``UC…`` channel id.

Inputs: channel_url (required), limit. Returns: object

youtube_get_transcriptC

Video transcript — port of tube-bridge youtube_get_transcript.

Layer: public timedtext. Quota: 0 units, no key.

Inputs: url (required), lang, with_timestamps. Returns: object

youtube_get_video_infoC

Rich metadata for one video — port of tube-bridge youtube_get_video_info.

Layer 1/2 (1 unit) → Layer 3 (yt-dlp, 0 units). ``duration`` in seconds.

Inputs: url (required). Returns: object

Tool Schema Changelog

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

Loading tool schema changelog…

    TDQS

    C2.7/5.0

    Scored across 15 tools

    Disambiguation2/5

    Several tool pairs have near-identical purposes: search_videos vs youtube_search (both video search, one with filters/fallback), get_channel_details vs channels_getChannel (both channel metadata by id/handle), and get_channel_rss_videos vs youtube_get_channel_videos (both 'recent uploads of a channel'). Descriptions explain layer/quota differences, but an agent choosing by intent alone would frequently misselect, and find_breakout_creators vs find_emerging_channels vs search_channels further blur the discovery space.

    Naming Consistency2/5

    Conventions are mixed: some tools carry a youtube_ namespace prefix (youtube_search, youtube_get_video_info), others are bare snake_case (search_videos, avg_duration), and one is camelCase with a resource prefix (channels_getChannel). Verb styles also vary (get_/find_/search_/avg_/max_), so the name gives little predictable signal about which layer or operation a tool uses.

    Tool Count3/5

    15 tools is at the upper edge of the reasonable band and the count is inflated by near-duplicate ports (two search tools, two channel-detail tools, two recent-uploads tools) rather than by distinct capabilities. A consolidated surface would need roughly 10-11 tools for the same coverage.

    Completeness4/5

    Coverage is broad for a YouTube data-collection server: search, channel/video metadata, transcripts, RSS, channel analytics (avg duration, max views, first publish date), discovery, and quota accounting. Gaps exist around playlists (only searchable, no playlist fetch/list) and comments, but core workflows are reachable.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      Enables AI assistants to search YouTube for videos, channels, and playlists while retrieving detailed analytics and metrics through the YouTube Data API v3. Supports advanced filtering options and provides comprehensive statistics for content discovery and analysis.
      1
      MIT
    • A
      license
      Not graded
      quality
      D
      maintenance
      Enables comprehensive YouTube data extraction and analysis using yt-dlp, including video metadata, channel statistics, playlists, comments, transcripts, search, trending videos, and engagement analytics with intelligent caching and batch processing.
      6
      MIT
    • A
      license
      A
      quality
      D
      maintenance
      Provides production-grade tools for YouTube channel resolution, video metadata extraction, transcripts, and playlist management. It features a quota-aware, AI-friendly design that supports structured searching and listing of public YouTube data.
      7
      2
      MIT