1919import yt_dlp
2020from yt_dlp .extractor .youtube import YoutubeIE
2121
22- from mcp_youtube_transcript import Transcript , VideoInfo , _parse_time_info
22+ from mcp_youtube_transcript import Transcript , VideoInfo , _parse_time_info , TimedTranscript , TranscriptSnippet
2323
2424
2525def fetch_title (url : str , lang : str ) -> str :
@@ -37,10 +37,20 @@ async def mcp_client_session() -> AsyncGenerator[ClientSession, None]:
3737 yield session
3838
3939
40+ @pytest .fixture (scope = "module" )
41+ async def mcp_client_session_with_response_limit () -> AsyncGenerator [ClientSession , None ]:
42+ params = StdioServerParameters (command = "uv" , args = ["run" , "mcp-youtube-transcript" , "--response-limit" , "3000" ])
43+ async with stdio_client (params ) as streams :
44+ async with ClientSession (streams [0 ], streams [1 ]) as session :
45+ await session .initialize ()
46+ yield session
47+
48+
4049@pytest .mark .anyio
4150async def test_list_tools (mcp_client_session : ClientSession ) -> None :
4251 res = await mcp_client_session .list_tools ()
4352 assert any (tool .name == "get_transcript" for tool in res .tools )
53+ assert any (tool .name == "get_timed_transcript" for tool in res .tools )
4454 assert any (tool .name == "get_video_info" for tool in res .tools )
4555
4656
@@ -158,15 +168,6 @@ async def test_get_transcript_with_short_url(mcp_client_session: ClientSession)
158168 assert not res .isError
159169
160170
161- @pytest .fixture (scope = "module" )
162- async def mcp_client_session_with_response_limit () -> AsyncGenerator [ClientSession , None ]:
163- params = StdioServerParameters (command = "uv" , args = ["run" , "mcp-youtube-transcript" , "--response-limit" , "3000" ])
164- async with stdio_client (params ) as streams :
165- async with ClientSession (streams [0 ], streams [1 ]) as session :
166- await session .initialize ()
167- yield session
168-
169-
170171@pytest .mark .skipif (os .getenv ("CI" ) == "true" , reason = "Skipping this test on CI" )
171172@pytest .mark .default_cassette ("LPZh9BOjkQs.yaml" )
172173@pytest .mark .vcr
@@ -199,6 +200,157 @@ async def test_get_transcript_with_response_limit(mcp_client_session_with_respon
199200 assert transcript [:- 1 ] == expect .transcript
200201
201202
203+ @pytest .mark .skipif (os .getenv ("CI" ) == "true" , reason = "Skipping this test on CI" )
204+ @pytest .mark .default_cassette ("LPZh9BOjkQs.yaml" )
205+ @pytest .mark .vcr
206+ @pytest .mark .anyio
207+ async def test_get_timed_transcript (mcp_client_session : ClientSession ) -> None :
208+ video_id = "LPZh9BOjkQs"
209+
210+ expect = TimedTranscript (
211+ title = fetch_title (video_id , "en" ),
212+ snippets = [TranscriptSnippet .from_fetched_transcript_snippet (s ) for s in YouTubeTranscriptApi ().fetch (video_id )],
213+ )
214+
215+ res = await mcp_client_session .call_tool (
216+ "get_timed_transcript" ,
217+ arguments = {"url" : f"https://www.youtube.com/watch?v={ video_id } " },
218+ )
219+ assert isinstance (res .content [0 ], TextContent )
220+
221+ transcript = TimedTranscript .model_validate_json (res .content [0 ].text )
222+ assert transcript == expect
223+ assert not res .isError
224+
225+
226+ @pytest .mark .skipif (os .getenv ("CI" ) == "true" , reason = "Skipping this test on CI" )
227+ @pytest .mark .default_cassette ("WjAXZkQSE2U.yaml" )
228+ @pytest .mark .vcr
229+ @pytest .mark .anyio
230+ async def test_get_timed_transcript_with_language (mcp_client_session : ClientSession ) -> None :
231+ video_id = "WjAXZkQSE2U"
232+
233+ expect = TimedTranscript (
234+ title = fetch_title (video_id , "ja" ),
235+ snippets = [
236+ TranscriptSnippet .from_fetched_transcript_snippet (s ) for s in YouTubeTranscriptApi ().fetch (video_id , ["ja" ])
237+ ],
238+ )
239+
240+ res = await mcp_client_session .call_tool (
241+ "get_timed_transcript" ,
242+ arguments = {"url" : f"https://www.youtube.com/watch?v={ video_id } " , "lang" : "ja" },
243+ )
244+ assert isinstance (res .content [0 ], TextContent )
245+ print (res .content [0 ].text )
246+
247+ transcript = TimedTranscript .model_validate_json (res .content [0 ].text )
248+ assert transcript == expect
249+ assert not res .isError
250+
251+
252+ @pytest .mark .skipif (os .getenv ("CI" ) == "true" , reason = "Skipping this test on CI" )
253+ @pytest .mark .default_cassette ("LPZh9BOjkQs.yaml" )
254+ @pytest .mark .vcr
255+ @pytest .mark .anyio
256+ async def test_get_timed_transcript_fallback_language (
257+ mcp_client_session : ClientSession ,
258+ ) -> None :
259+ video_id = "LPZh9BOjkQs"
260+
261+ expect = TimedTranscript (
262+ title = fetch_title (video_id , "en" ),
263+ snippets = [TranscriptSnippet .from_fetched_transcript_snippet (s ) for s in YouTubeTranscriptApi ().fetch (video_id )],
264+ )
265+
266+ res = await mcp_client_session .call_tool (
267+ "get_timed_transcript" ,
268+ arguments = {
269+ "url" : f"https://www.youtube.com/watch?v={ video_id } " ,
270+ "lang" : "unknown" ,
271+ },
272+ )
273+ assert isinstance (res .content [0 ], TextContent )
274+
275+ transcript = TimedTranscript .model_validate_json (res .content [0 ].text )
276+ assert transcript == expect
277+ assert not res .isError
278+
279+
280+ @pytest .mark .anyio
281+ async def test_get_timed_transcript_invalid_url (mcp_client_session : ClientSession ) -> None :
282+ res = await mcp_client_session .call_tool (
283+ "get_timed_transcript" , arguments = {"url" : "https://www.youtube.com/watch?vv=abcdefg" }
284+ )
285+ assert res .isError
286+
287+
288+ @pytest .mark .skipif (os .getenv ("CI" ) == "true" , reason = "Skipping this test on CI" )
289+ @pytest .mark .default_cassette ("error.yaml" )
290+ @pytest .mark .vcr
291+ @pytest .mark .anyio
292+ async def test_get_timed_transcript_not_found (mcp_client_session : ClientSession ) -> None :
293+ res = await mcp_client_session .call_tool (
294+ "get_timed_transcript" , arguments = {"url" : "https://www.youtube.com/watch?v=a" }
295+ )
296+ assert res .isError
297+
298+
299+ @pytest .mark .skipif (os .getenv ("CI" ) == "true" , reason = "Skipping this test on CI" )
300+ @pytest .mark .default_cassette ("LPZh9BOjkQs.yaml" )
301+ @pytest .mark .vcr
302+ @pytest .mark .anyio
303+ async def test_get_timed_transcript_with_short_url (mcp_client_session : ClientSession ) -> None :
304+ video_id = "LPZh9BOjkQs"
305+
306+ expect = TimedTranscript (
307+ title = fetch_title (video_id , "en" ),
308+ snippets = [TranscriptSnippet .from_fetched_transcript_snippet (s ) for s in YouTubeTranscriptApi ().fetch (video_id )],
309+ )
310+
311+ res = await mcp_client_session .call_tool (
312+ "get_timed_transcript" ,
313+ arguments = {"url" : f"https://youtu.be/{ video_id } " },
314+ )
315+ assert isinstance (res .content [0 ], TextContent )
316+
317+ transcript = TimedTranscript .model_validate_json (res .content [0 ].text )
318+ assert transcript == expect
319+ assert not res .isError
320+
321+
322+ @pytest .mark .skipif (os .getenv ("CI" ) == "true" , reason = "Skipping this test on CI" )
323+ @pytest .mark .default_cassette ("LPZh9BOjkQs.yaml" )
324+ @pytest .mark .vcr
325+ @pytest .mark .anyio
326+ async def test_get_timed_transcript_with_response_limit (mcp_client_session_with_response_limit : ClientSession ) -> None :
327+ video_id = "LPZh9BOjkQs"
328+
329+ expect = TimedTranscript (
330+ title = fetch_title (video_id , "en" ),
331+ snippets = [TranscriptSnippet .from_fetched_transcript_snippet (s ) for s in YouTubeTranscriptApi ().fetch (video_id )],
332+ )
333+
334+ snippets = []
335+ cursor = None
336+ while True :
337+ res = await mcp_client_session_with_response_limit .call_tool (
338+ "get_timed_transcript" ,
339+ arguments = {"url" : f"https://www.youtube.com/watch?v={ video_id } " , "next_cursor" : cursor },
340+ )
341+ assert not res .isError
342+ assert isinstance (res .content [0 ], TextContent )
343+
344+ t = TimedTranscript .model_validate_json (res .content [0 ].text )
345+ snippets .extend (t .snippets )
346+ if t .next_cursor is None :
347+ break
348+ cursor = t .next_cursor
349+
350+ assert t .title == expect .title
351+ assert snippets == expect .snippets
352+
353+
202354@pytest .mark .skipif (os .getenv ("CI" ) == "true" , reason = "Skipping this test on CI" )
203355@pytest .mark .anyio
204356async def test_get_video_info (mcp_client_session : ClientSession ) -> None :
0 commit comments