Skip to content

get_transcript returns empty text: YouTube now IP-binds signed caption URLs #3

Description

@SimonGeiger

Hey, thank you for putting this together and sharing it publicly — digging into it and trying to get transcripts working was a genuinely interesting MCP server learning experience for me. Unfortunately I wasn't able to get get_transcript working in my own testing, so I wanted to share what I found in case it's useful to you or anyone else running into the same thing.

Claude's Assessment: This report was investigated and together with Claude

get_transcript silently returns empty text for every video

What I found

get_transcript currently returns an empty string for every video I tried, with no error surfaced anywhere — the server responds 200 OK, so nothing looks broken from the outside, but the transcript field is blank.

What seems to be causing it

src/lib/youtube.ts uses the youtube-transcript npm package (v1.2.1), which scrapes the video's watch page for a captionTracks list, then fetches the transcript text from a signed URL embedded there.

Digging into that signed URL, it now has an IP binding baked into its signature:

https://www.youtube.com/api/timedtext?v=...&ip=0.0.0.0&ipbits=0&...&sparams=ip,ipbits,expire,...&signature=...

My read is: when the request isn't coming from what YouTube considers a genuine browser session, it issues the URL pre-signed against a dummy 0.0.0.0 address. The follow-up fetch to that URL still returns 200 OK, but with an empty body — no error, just silently no captions. I confirmed this by fetching the signed URL directly across multiple videos and multiple fmt= variants (json3, vtt, srv1, srv3); none returned content, and adding Referer/Origin/cookie headers to mimic a browser didn't change the outcome either.

My best guess is this is YouTube hardening against unofficial scraping generally (cloud/datacenter IP ranges — AWS, GCP, Azure, and by extension Cloudflare Workers — are commonly blocked/degraded this way), rather than anything specific to this repo's request shape — but I can't say that with certainty.

What I tried next

In my own fork, I swapped youtube-transcript for youtubei.js (calls YouTube's internal InnerTube API instead of scraping the page). That resolved the specific issue above — video metadata and caption-track listing worked fine — but info.getTranscript() then hit a hard 400 from YouTube's get_transcript endpoint specifically, across all client contexts I tested (WEB/IOS/TV/ANDROID). My guess is that endpoint now needs a PoToken (proof-of-origin token), which looks like a substantially bigger fix (BotGuard attestation) than a library swap, and seems to be an open problem across the wider YouTube-scraping ecosystem right now rather than something unique to this repo.

I'm happy to share the diff for the youtubei.js swap if it's useful as a starting point, even though it doesn't fully resolve things on its own — it at least turns "silently returns wrong data" into a clear thrown error.

To reproduce

curl -X POST https://<your-worker>/mcp -H "Content-Type: application/json" -d '{
  "jsonrpc":"2.0","id":1,"method":"tools/call",
  "params":{"name":"get_transcript","arguments":{"url":"https://www.youtube.com/watch?v=dQw4w9WgXcQ","language":"en"}}
}'

Returns 200 OK with result.content[0].text empty.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions