yt-sub-mcp
Provides tools for retrieving and searching YouTube video subtitles, including listing available subtitle tracks, fetching subtitle text with timestamps, and searching for specific expressions within subtitles.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@yt-sub-mcpWhat are the subtitles for this video? https://youtu.be/jNQXAC9IVRw"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
yt-sub-mcp
유튜브 자막을 읽는 MCP 서버.
YouTube 웹 UI의 "스크립트 표시" 패널이 쓰는 내부 엔드포인트(youtubei/v1/get_panel)를 사용합니다. 흔히 쓰이는 /api/timedtext 경로와 달리 레이트리밋에 걸리지 않고, 4시간짜리 영상도 요청 한 번에 전체 자막이 옵니다. 왜 이 경로를 쓰게 됐는지는 문제 해결 과정에 적어뒀습니다.
인증 불필요 — 쿠키도, API 키 발급도, 로그인도 없음
외부 바이너리 없음 — yt-dlp도 ffmpeg도 안 씁니다
영상당 요청 2회, 응답은 SQLite에 영속 캐싱
설치
git clone https://github.com/<you>/yt-sub-mcp.git
cd yt-sub-mcp
uv syncClaude Code에 등록:
claude mcp add yt-sub -- uv --directory /absolute/path/to/yt-sub-mcp run yt-sub-mcpClaude Desktop이라면 claude_desktop_config.json에:
{
"mcpServers": {
"yt-sub": {
"command": "uv",
"args": ["--directory", "/absolute/path/to/yt-sub-mcp", "run", "yt-sub-mcp"]
}
}
}Related MCP server: tubescribe-mcp
도구
list_subtitle_tracks(video)
사용 가능한 자막 언어 목록. 수동 제작인지 자동 생성인지 구분해서 보여줍니다.
2 track(s) for jNQXAC9IVRw:
de German (manual, translatable)
en English (manual, translatable)get_subtitles(video, lang, timestamps, start_time, end_time, max_chars, refresh)
자막 본문.
인자 | 기본값 | 설명 |
| — | URL 또는 11자 ID |
|
| 우선순위 순 콤마 구분 ( |
|
| 블록마다 시작 시각 표시 |
| — | 초 단위 구간 |
|
| 문자 예산 |
|
| 캐시 우회 |
Korean (ko), manual | length 20:03 | excerpt 4:56-5:44
[4:56] 아무튼 그 중에 3명의 동방박사가 등장하는 장면 아시죠? ...search_subtitles(video, query, lang, context, max_hits)
특정 표현이 나오는 지점을 타임스탬프·딥링크와 함께 반환합니다. 전체를 읽는 것보다 훨씬 쌉니다.
5 match(es) for 'creativity' in iG9CE55wbtY, in 4 passage(s):
[0:48] ... the extraordinary evidence of human creativity in all of ...
https://www.youtube.com/watch?v=iG9CE55wbtY&t=48svideo는 watch?v=, youtu.be, shorts, embed, live, 그리고 맨 ID를 모두 받습니다.
토큰 예산
1시간짜리 영상 자막은 대략 15k 토큰입니다. 그래서:
출력은
max_chars에서 잘리고 이어볼 타임스탬프를 함께 반환합니다. 그 값을start_time으로 넣으면 이어집니다.평문은 ~90단어 문단, 타임스탬프 모드는 ~30초 구간으로 병합해 렌더링합니다. 큐마다 타임스탬프를 붙이면 토큰이 3배가 됩니다.
timestamps는 기본 꺼짐. 인용이나 링크가 필요할 때만 켜세요.권장 흐름:
search_subtitles로 위치를 찾고 → 그 구간만get_subtitles로 읽기.
캐시
메모리 LRU(32) 앞단 + SQLite 영속 계층(500) 2단 구조입니다.
실측: 19분 영상 첫 요청 1,301ms → 이후 프로세스에서 0.7ms.
자막은 [text, start, duration] 삼중항 JSON을 zlib 압축해 저장합니다(427큐 25KB → 11KB, 43%).
환경변수 | 기본값 | 용도 |
|
|
|
| 아래 | 캐시 위치 |
|
| 만료. |
기본 경로는 macOS ~/Library/Caches/yt-sub-mcp/, 그 외 $XDG_CACHE_HOME/yt-sub-mcp/.
캐시는 기능을 죽이지 않습니다. 디스크가 차거나 DB를 열 수 없으면 로그만 남기고 메모리 전용으로 degrade합니다.
문제 해결 과정
이 프로젝트의 설계는 대부분 실패에서 나왔습니다. 기록해둡니다.
1. 처음엔 youtube-transcript-api로 만들었다
가장 흔한 선택입니다. 잘 돌아갔습니다 — 개발 도중 IP가 차단되기 전까지는.
HTTP 429 Too Many Requests on /api/timedtext약 25분간 도구 호출 20여 회 만에 걸렸습니다. 라이브러리는 이걸 IpBlocked라 부르지만 실제로는 단순 429, 즉 볼륨 기반 쿨다운입니다.
2. 차단의 범위를 좁혔다
호출당 HTTP 요청을 계측해보니:
list_subtitle_tracks = 2 요청 (watch 페이지 → INNERTUBE player)
get_subtitles (cold) = 3 요청 (위 2개 + /api/timedtext)그리고 차단 중에도 watch 페이지와 InnerTube player는 정상 응답했습니다. 즉 429는 /api/timedtext 한 엔드포인트에만 걸립니다.
3. yt-dlp로 갈아타려다 접었다
유지보수가 활발하고 PO token·쿠키를 지원하니 답일 것 같았습니다. 그런데 yt-dlp도 player 응답에서 받은 timedtext URL을 그대로 씁니다. 같은 429를 맞았습니다.
클라이언트 로테이션도 시험했습니다. 12종 중 추출에 성공한 둘 모두:
android 추출 OK | timedtext 429
android_vr 추출 OK | timedtext 429제한이 URL을 발급한 클라이언트가 아니라 IP에 걸려 있으니 당연한 결과였습니다. 마이그레이션은 취소했습니다.
4. get_transcript를 찾았지만 죽어 있었다
"InnerTube 스크립트 API는 레이트리밋이 느슨하다"는 자료가 여럿 있었습니다. protobuf params를 직접 만들어 YouTube가 watch 페이지에 심어둔 값과 대조했더니 바이트 단위로 일치했는데도 전부 400이었습니다.
player (control) HTTP 200
next (control) HTTP 200
get_transcript HTTP 400
get_transcript+bad HTTP 400 ← 쓰레기 params와 동일 응답정상 params와 쓰레기 params의 응답이 같다는 건 본문 검증 이전에 거부된다는 뜻입니다. get_transcript는 폐기된 엔드포인트였고, 그걸 소개한 자료들이 낡은 것이었습니다.
5. 브라우저 캡처가 답을 줬다
실제 "스크립트 표시" 버튼을 누른 요청을 보니 엔드포인트가 교체돼 있었습니다:
POST /youtubei/v1/get_panel?prettyPrint=false
{ "panelId": "PAmodern_transcript_view", "params": "qgkPCgtNcXFXeFpMeldRTRgC" }params를 디코딩하니 field 149 { 1: video_id, 3: 2 }, 이게 전부였습니다. 기존 get_transcript보다 훨씬 단순합니다.
6. 검증: timedtext가 429인 상태에서 전부 성공
|
| |
레이트리밋 | 25~40요청 후 429 | 25요청 / 425분 분량 / 5초 — 무차단 |
인증 | 불필요 | 불필요 ( |
영상당 요청 | 3 | 2 |
4.4시간 영상 | — | 1요청, 2.6MB, 2053큐 완전 |
타임스탬프 | ms + duration | 초 단위, end 없음 |
큐 수 (20분 TED) | 427 | 163 (읽기 좋게 병합됨) |
위 측정은 모두 timedtext가 429로 막힌 상태에서 나왔습니다. 두 엔드포인트가 별도 예산이라는 뜻입니다.
남은 트레이드오프
타임스탬프가 초 단위이고 end 시각이 없습니다. 응답 어디에도 ms 필드가 없습니다. 각 큐는 다음 큐가 시작할 때까지, 마지막 큐는 영상 끝까지로 길이를 추론합니다. 표시가 M:SS이고 딥링크가 &t=Ns라 실질 손실은 작습니다.
없는 언어를 요청하면 조용히 기본 트랙으로 폴백합니다. 응답에 언어 표시가 전혀 없어서, hl을 던지기 전에 player 응답으로 트랙 목록을 먼저 확인하고 없으면 거부합니다. 영상당 요청이 2회인 이유가 이것입니다.
비공식 내부 엔드포인트입니다. 언제든 바뀔 수 있습니다 — get_transcript가 그렇게 죽었습니다. 다만 watch 페이지에서 params를 재추출하는 방법을 알고 있어 복구 경로가 있고, ProviderChain이 대체 소스를 끼울 자리를 남겨둡니다.
구조
providers/base.py SubtitleProvider 프로토콜 + ProviderChain
providers/get_panel.py 유일한 provider — protobuf params, 파싱, 트랙 선택
errors.py provider 중립 에러 + retryable 플래그
models.py provider 중립 타입
formatting.py 병합 / 구간 슬라이싱 / 문자 예산
cache.py 메모리 LRU + SQLite + 합성 스토어
urls.py URL·ID 파싱
server.py MCP 도구models.py 타입과 errors.py 예외만 provider 경계를 넘습니다. 백엔드 고유 예외는 provider 안에서 번역됩니다.
provider가 하나뿐이라 ProviderChain은 지금 사실상 이음새입니다. 남겨둔 이유는 재시도 정책(SubtitleError.retryable)을 소유하고 있고, 두 번째 소스(timedtext 리더, 자막 없는 영상용 Whisper)를 도구 코드 변경 없이 끼울 자리이기 때문입니다.
폴백 정책
상황 | 에러 | 다음 provider 시도 |
429 / 연령 제한 / 로그인 요구 |
| ✅ |
요청 언어 없음 |
| ✅ |
네트워크·파싱 오류 |
| ✅ |
영상 삭제/비공개 |
| ❌ |
자막이 꺼져 있음 |
| ❌ |
영구 실패에 다른 provider를 태워봐야 같은 답이므로 즉시 중단합니다.
개발
uv sync --dev
uv run pytest
uv run ruff check .
uv run ruff format .서버가 실제로 뜨는지 확인:
uv run python scripts/smoke_stdio.py테스트는 전부 오프라인입니다. conftest.py가 디스크 캐시를 끄고(개발자의 실제 캐시를 건드리지 않도록), provider 테스트는 캡처한 응답을 씁니다. CI에서 네트워크 호출이 생기면 테스트가 퇴행한 것입니다.
CI는 Linux/macOS × Python 3.12/3.13에서 lint·format·test를 돌리고, 별도 job이 stdio로 서버를 띄워 도구 목록을 확인합니다.
라이선스
MIT
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseAqualityBmaintenanceAn MCP server (stdio + HTTP/SSE) that fetches video transcripts/subtitles via yt-dlp, with pagination for large responses. Supports YouTube, Twitter/X, Instagram, TikTok, Twitch, Vimeo, Facebook, Bilibili, VK, Dailymotion. Whisper fallback — transcribes audio when subtitles are unavailable (local or OpenAI API). Works with Cursor and other MCP host818MIT
- Alicense-qualityCmaintenanceMCP server for fetching YouTube video transcripts without an API key.GPL 3.0
- Flicense-qualityDmaintenanceMCP server providing tools to fetch YouTube video transcripts with metadata, supporting direct YouTube transcripts and audio transcription via multiple backends (whisper, AssemblyAI, OpenAI, Gemini).
- Alicense-qualityDmaintenanceA minimalist MCP server that fetches transcripts from YouTube videos using the Supadata API.9MIT
Related MCP Connectors
YouTube MCP — wraps the YouTube Data API v3 (BYO API key)
MCP server for Clipkit — gives AI agents a video toolbox via the Clipkit schema.
MCP server for Hailuo (MiniMax) AI video generation
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/YaleDevUni/yt-sub-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server