Skip to main content
Glama

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 sync

Claude Code에 등록:

claude mcp add yt-sub -- uv --directory /absolute/path/to/yt-sub-mcp run yt-sub-mcp

Claude 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)

자막 본문.

인자

기본값

설명

video

URL 또는 11자 ID

lang

"en"

우선순위 순 콤마 구분 ("ko,en")

timestamps

false

블록마다 시작 시각 표시

start_time / end_time

초 단위 구간

max_chars

20000

문자 예산

refresh

false

캐시 우회

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=48s

videowatch?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%).

환경변수

기본값

용도

YT_SUB_MCP_CACHE

1

0이면 디스크 계층 끔

YT_SUB_MCP_CACHE_DIR

아래

캐시 위치

YT_SUB_MCP_CACHE_TTL_DAYS

30

만료. 0이면 무기한

기본 경로는 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인 상태에서 전부 성공

/api/timedtext

/youtubei/v1/get_panel

레이트리밋

25~40요청 후 429

25요청 / 425분 분량 / 5초 — 무차단

인증

불필요

불필요 (logged_in=0 확인)

영상당 요청

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 / 연령 제한 / 로그인 요구

AccessBlocked

요청 언어 없음

LanguageNotAvailable

네트워크·파싱 오류

ProviderFailure

영상 삭제/비공개

VideoNotFound

자막이 꺼져 있음

SubtitlesDisabled

영구 실패에 다른 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

A
license - permissive license
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

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

View all related MCP servers

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

View all MCP Connectors

Latest Blog Posts

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