youtube-context-mcp 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,36 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ['3.12', '3.13', '3.14']
15
+ env:
16
+ UV_PYTHON: ${{ matrix.python-version }}
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ - name: Install uv
21
+ uses: astral-sh/setup-uv@v5
22
+
23
+ - name: Install dependencies
24
+ run: uv sync
25
+
26
+ - name: Lint
27
+ run: uv run ruff check .
28
+
29
+ - name: Check formatting
30
+ run: uv run ruff format --check .
31
+
32
+ - name: Run tests
33
+ run: uv run pytest
34
+
35
+ - name: Build package
36
+ run: uv build
@@ -0,0 +1,23 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+ permissions:
11
+ id-token: write
12
+
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ - name: Install uv
17
+ uses: astral-sh/setup-uv@v5
18
+
19
+ - name: Build package
20
+ run: uv build
21
+
22
+ - name: Publish to PyPI
23
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,10 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
@@ -0,0 +1 @@
1
+ 3.14
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Onur Cetinkol
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,140 @@
1
+ Metadata-Version: 2.4
2
+ Name: youtube-context-mcp
3
+ Version: 0.2.0
4
+ Summary: A small MCP server for fetching YouTube video transcripts, wrapping youtube-transcript-api
5
+ Project-URL: Homepage, https://github.com/realiti4/youtube-context-mcp
6
+ Project-URL: Repository, https://github.com/realiti4/youtube-context-mcp
7
+ Project-URL: Issues, https://github.com/realiti4/youtube-context-mcp/issues
8
+ Author-email: Onur Cetinkol <onurcetinkol@gmail.com>
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: mcp,model-context-protocol,subtitles,transcript,youtube
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Topic :: Multimedia :: Video
22
+ Classifier: Topic :: Utilities
23
+ Requires-Python: >=3.12
24
+ Requires-Dist: mcp>=1.12
25
+ Requires-Dist: requests>=2.32
26
+ Requires-Dist: youtube-transcript-api<2,>=1.2.4
27
+ Description-Content-Type: text/markdown
28
+
29
+ # youtube-context-mcp
30
+
31
+ A small [MCP](https://modelcontextprotocol.io) server that lets agents read YouTube video
32
+ transcripts so you can ask questions about a video, summarize it, or pull quotes.
33
+
34
+ It's a thin wrapper around [`youtube-transcript-api`](https://github.com/jdepoix/youtube-transcript-api),
35
+ which does all the actual fetching. This project just exposes it as two MCP tools.
36
+
37
+ > It returns a video's **existing captions/subtitles** — it does **not** transcribe audio
38
+ > (no Whisper/ASR). Videos without captions have nothing to return.
39
+
40
+ ## Install
41
+
42
+ Run it on demand with [uv](https://docs.astral.sh/uv/) (no install needed):
43
+
44
+ ```bash
45
+ uvx youtube-context-mcp
46
+ ```
47
+
48
+ Or install it:
49
+
50
+ ```bash
51
+ pip install youtube-context-mcp
52
+ ```
53
+
54
+ ## Use it with an agent
55
+
56
+ Add it to your MCP client config:
57
+
58
+ ```json
59
+ {
60
+ "mcpServers": {
61
+ "youtube-context": {
62
+ "command": "uvx",
63
+ "args": ["youtube-context-mcp"]
64
+ }
65
+ }
66
+ }
67
+ ```
68
+
69
+ Or, in Claude Code:
70
+
71
+ ```bash
72
+ claude mcp add youtube-context -- uvx youtube-context-mcp
73
+ ```
74
+
75
+ ### Running over HTTP
76
+
77
+ By default the server talks **stdio** (the client launches it). If your client runs on a
78
+ different host — for example LM Studio on Windows while this server runs in WSL2 — run it as a
79
+ long-lived HTTP server instead and point the client at a URL:
80
+
81
+ ```bash
82
+ youtube-context-mcp --transport http --host 0.0.0.0 --port 8000
83
+ ```
84
+
85
+ Then add it by URL:
86
+
87
+ ```json
88
+ {
89
+ "mcpServers": {
90
+ "youtube-context": { "url": "http://localhost:8000/mcp" }
91
+ }
92
+ }
93
+ ```
94
+
95
+ (`--host 0.0.0.0` makes it reachable from the Windows side; WSL2 forwards `localhost`.)
96
+
97
+ ## Tools
98
+
99
+ | Tool | What it does |
100
+ | --- | --- |
101
+ | `get_transcript(video, languages=["en"], include_timestamps=False, translate_to=None)` | Returns the transcript as text. `video` is a URL or 11-char ID. Set `include_timestamps` for `[mm:ss]` / `[h:mm:ss]` lines; `translate_to` for an ISO language code. |
102
+ | `list_transcripts(video)` | Lists available transcripts (language, code, manual vs auto-generated, translatable) plus the translation targets. Use it when `get_transcript` can't find your language. |
103
+
104
+ ## Proxies (optional)
105
+
106
+ YouTube blocks most datacenter/cloud IPs, so on a server you may hit `RequestBlocked` /
107
+ `IpBlocked`. Locally this is rarely needed. To route requests through a proxy, set env vars:
108
+
109
+ | Env var | Purpose |
110
+ | --- | --- |
111
+ | `WEBSHARE_PROXY_USERNAME`, `WEBSHARE_PROXY_PASSWORD` | Use [Webshare](https://www.webshare.io/) rotating residential proxies. |
112
+ | `WEBSHARE_PROXY_LOCATIONS` | Optional CSV of country codes, e.g. `us,de`. |
113
+ | `YT_TRANSCRIPT_HTTP_PROXY`, `YT_TRANSCRIPT_HTTPS_PROXY` | Use a generic HTTP/HTTPS proxy instead. |
114
+ | `YT_TRANSCRIPT_TIMEOUT` | Per-request timeout in seconds (default `20`). |
115
+
116
+ With no env set, requests go out directly.
117
+
118
+ ## Troubleshooting
119
+
120
+ - **`RequestBlocked` / `IpBlocked`** — YouTube blocked the IP. Set the proxy env vars above.
121
+ - **No transcript found** — call `list_transcripts` to see which languages exist for that video.
122
+ - **Transcripts disabled** — the uploader turned captions off; nothing can be fetched.
123
+
124
+ ## Development
125
+
126
+ ```bash
127
+ uv sync
128
+ uv run ruff check . && uv run ruff format --check .
129
+ uv run pytest
130
+ uv run mcp dev src/youtube_context_mcp/server.py --with-editable . # interactive inspector
131
+ ```
132
+
133
+ ## License
134
+
135
+ MIT
136
+
137
+ ## Credits
138
+
139
+ All transcript fetching is done by [`youtube-transcript-api`](https://github.com/jdepoix/youtube-transcript-api)
140
+ by Jonas Depoix. This project is just an MCP adapter on top of it.
@@ -0,0 +1,112 @@
1
+ # youtube-context-mcp
2
+
3
+ A small [MCP](https://modelcontextprotocol.io) server that lets agents read YouTube video
4
+ transcripts so you can ask questions about a video, summarize it, or pull quotes.
5
+
6
+ It's a thin wrapper around [`youtube-transcript-api`](https://github.com/jdepoix/youtube-transcript-api),
7
+ which does all the actual fetching. This project just exposes it as two MCP tools.
8
+
9
+ > It returns a video's **existing captions/subtitles** — it does **not** transcribe audio
10
+ > (no Whisper/ASR). Videos without captions have nothing to return.
11
+
12
+ ## Install
13
+
14
+ Run it on demand with [uv](https://docs.astral.sh/uv/) (no install needed):
15
+
16
+ ```bash
17
+ uvx youtube-context-mcp
18
+ ```
19
+
20
+ Or install it:
21
+
22
+ ```bash
23
+ pip install youtube-context-mcp
24
+ ```
25
+
26
+ ## Use it with an agent
27
+
28
+ Add it to your MCP client config:
29
+
30
+ ```json
31
+ {
32
+ "mcpServers": {
33
+ "youtube-context": {
34
+ "command": "uvx",
35
+ "args": ["youtube-context-mcp"]
36
+ }
37
+ }
38
+ }
39
+ ```
40
+
41
+ Or, in Claude Code:
42
+
43
+ ```bash
44
+ claude mcp add youtube-context -- uvx youtube-context-mcp
45
+ ```
46
+
47
+ ### Running over HTTP
48
+
49
+ By default the server talks **stdio** (the client launches it). If your client runs on a
50
+ different host — for example LM Studio on Windows while this server runs in WSL2 — run it as a
51
+ long-lived HTTP server instead and point the client at a URL:
52
+
53
+ ```bash
54
+ youtube-context-mcp --transport http --host 0.0.0.0 --port 8000
55
+ ```
56
+
57
+ Then add it by URL:
58
+
59
+ ```json
60
+ {
61
+ "mcpServers": {
62
+ "youtube-context": { "url": "http://localhost:8000/mcp" }
63
+ }
64
+ }
65
+ ```
66
+
67
+ (`--host 0.0.0.0` makes it reachable from the Windows side; WSL2 forwards `localhost`.)
68
+
69
+ ## Tools
70
+
71
+ | Tool | What it does |
72
+ | --- | --- |
73
+ | `get_transcript(video, languages=["en"], include_timestamps=False, translate_to=None)` | Returns the transcript as text. `video` is a URL or 11-char ID. Set `include_timestamps` for `[mm:ss]` / `[h:mm:ss]` lines; `translate_to` for an ISO language code. |
74
+ | `list_transcripts(video)` | Lists available transcripts (language, code, manual vs auto-generated, translatable) plus the translation targets. Use it when `get_transcript` can't find your language. |
75
+
76
+ ## Proxies (optional)
77
+
78
+ YouTube blocks most datacenter/cloud IPs, so on a server you may hit `RequestBlocked` /
79
+ `IpBlocked`. Locally this is rarely needed. To route requests through a proxy, set env vars:
80
+
81
+ | Env var | Purpose |
82
+ | --- | --- |
83
+ | `WEBSHARE_PROXY_USERNAME`, `WEBSHARE_PROXY_PASSWORD` | Use [Webshare](https://www.webshare.io/) rotating residential proxies. |
84
+ | `WEBSHARE_PROXY_LOCATIONS` | Optional CSV of country codes, e.g. `us,de`. |
85
+ | `YT_TRANSCRIPT_HTTP_PROXY`, `YT_TRANSCRIPT_HTTPS_PROXY` | Use a generic HTTP/HTTPS proxy instead. |
86
+ | `YT_TRANSCRIPT_TIMEOUT` | Per-request timeout in seconds (default `20`). |
87
+
88
+ With no env set, requests go out directly.
89
+
90
+ ## Troubleshooting
91
+
92
+ - **`RequestBlocked` / `IpBlocked`** — YouTube blocked the IP. Set the proxy env vars above.
93
+ - **No transcript found** — call `list_transcripts` to see which languages exist for that video.
94
+ - **Transcripts disabled** — the uploader turned captions off; nothing can be fetched.
95
+
96
+ ## Development
97
+
98
+ ```bash
99
+ uv sync
100
+ uv run ruff check . && uv run ruff format --check .
101
+ uv run pytest
102
+ uv run mcp dev src/youtube_context_mcp/server.py --with-editable . # interactive inspector
103
+ ```
104
+
105
+ ## License
106
+
107
+ MIT
108
+
109
+ ## Credits
110
+
111
+ All transcript fetching is done by [`youtube-transcript-api`](https://github.com/jdepoix/youtube-transcript-api)
112
+ by Jonas Depoix. This project is just an MCP adapter on top of it.
@@ -0,0 +1,60 @@
1
+ [project]
2
+ name = "youtube-context-mcp"
3
+ version = "0.2.0"
4
+ description = "A small MCP server for fetching YouTube video transcripts, wrapping youtube-transcript-api"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ dependencies = [
8
+ "mcp>=1.12",
9
+ "youtube-transcript-api>=1.2.4,<2",
10
+ "requests>=2.32",
11
+ ]
12
+ authors = [{name = "Onur Cetinkol", email = "onurcetinkol@gmail.com"}]
13
+ license = {text = "MIT"}
14
+ keywords = ["youtube", "transcript", "mcp", "model-context-protocol", "subtitles"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Environment :: Console",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Operating System :: OS Independent",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Programming Language :: Python :: 3.14",
25
+ "Topic :: Multimedia :: Video",
26
+ "Topic :: Utilities",
27
+ ]
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/realiti4/youtube-context-mcp"
31
+ Repository = "https://github.com/realiti4/youtube-context-mcp"
32
+ Issues = "https://github.com/realiti4/youtube-context-mcp/issues"
33
+
34
+ [project.scripts]
35
+ youtube-context-mcp = "youtube_context_mcp.server:main"
36
+
37
+ [build-system]
38
+ requires = ["hatchling"]
39
+ build-backend = "hatchling.build"
40
+
41
+ [tool.hatch.build.targets.wheel]
42
+ packages = ["src/youtube_context_mcp"]
43
+
44
+ [dependency-groups]
45
+ dev = [
46
+ "pytest>=8.0",
47
+ "ruff>=0.6",
48
+ "mcp[cli]>=1.12",
49
+ ]
50
+
51
+ [tool.pytest.ini_options]
52
+ testpaths = ["tests"]
53
+ pythonpath = ["src"]
54
+
55
+ [tool.ruff]
56
+ line-length = 100
57
+ target-version = "py312"
58
+
59
+ [tool.ruff.lint]
60
+ select = ["E", "F", "W", "I"]
@@ -0,0 +1,7 @@
1
+ """A small MCP server for YouTube transcripts, wrapping youtube-transcript-api."""
2
+
3
+ from importlib.metadata import version
4
+
5
+ __version__ = version("youtube-context-mcp")
6
+
7
+ __all__ = ["__version__"]
@@ -0,0 +1,6 @@
1
+ """Entry point for `python -m youtube_context_mcp`."""
2
+
3
+ from youtube_context_mcp.server import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -0,0 +1,43 @@
1
+ """Build a youtube-transcript-api proxy config from environment variables.
2
+
3
+ YouTube blocks most datacenter/cloud IPs, so on a server the underlying library can raise
4
+ ``RequestBlocked`` / ``IpBlocked``. These env vars let the user opt into a proxy without any
5
+ code change. With nothing set, requests go out directly.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import os
11
+
12
+ from youtube_transcript_api.proxies import (
13
+ GenericProxyConfig,
14
+ ProxyConfig,
15
+ WebshareProxyConfig,
16
+ )
17
+
18
+
19
+ def build_proxy_config() -> ProxyConfig | None:
20
+ """Return a ``ProxyConfig`` derived from env vars, or ``None`` for direct requests.
21
+
22
+ Precedence:
23
+ 1. ``WEBSHARE_PROXY_USERNAME`` + ``WEBSHARE_PROXY_PASSWORD`` -> ``WebshareProxyConfig``
24
+ (optional ``WEBSHARE_PROXY_LOCATIONS``, a CSV of country codes such as ``us,de``).
25
+ 2. ``YT_TRANSCRIPT_HTTP_PROXY`` / ``YT_TRANSCRIPT_HTTPS_PROXY`` -> ``GenericProxyConfig``.
26
+ """
27
+ webshare_user = os.environ.get("WEBSHARE_PROXY_USERNAME")
28
+ webshare_pass = os.environ.get("WEBSHARE_PROXY_PASSWORD")
29
+ if webshare_user and webshare_pass:
30
+ locations = os.environ.get("WEBSHARE_PROXY_LOCATIONS", "")
31
+ filter_ip_locations = [c.strip() for c in locations.split(",") if c.strip()]
32
+ return WebshareProxyConfig(
33
+ proxy_username=webshare_user,
34
+ proxy_password=webshare_pass,
35
+ filter_ip_locations=filter_ip_locations or None,
36
+ )
37
+
38
+ http_proxy = os.environ.get("YT_TRANSCRIPT_HTTP_PROXY")
39
+ https_proxy = os.environ.get("YT_TRANSCRIPT_HTTPS_PROXY")
40
+ if http_proxy or https_proxy:
41
+ return GenericProxyConfig(http_url=http_proxy, https_url=https_proxy)
42
+
43
+ return None
@@ -0,0 +1,114 @@
1
+ """MCP server exposing YouTube transcript tools over stdio."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from typing import TypedDict
7
+
8
+ from mcp.server.fastmcp import FastMCP
9
+
10
+ from youtube_context_mcp import transcripts
11
+
12
+ mcp = FastMCP("youtube-context")
13
+
14
+
15
+ class TranscriptInfo(TypedDict):
16
+ language: str
17
+ language_code: str
18
+ is_generated: bool
19
+ is_translatable: bool
20
+
21
+
22
+ class TranslationLanguage(TypedDict):
23
+ language: str
24
+ language_code: str
25
+
26
+
27
+ class TranscriptListing(TypedDict):
28
+ transcripts: list[TranscriptInfo]
29
+ translation_languages: list[TranslationLanguage]
30
+
31
+
32
+ @mcp.tool(structured_output=False)
33
+ def get_transcript(
34
+ video: str,
35
+ languages: list[str] | None = None,
36
+ include_timestamps: bool = False,
37
+ translate_to: str | None = None,
38
+ ) -> str:
39
+ """Fetch a YouTube video's existing captions as text so you can answer questions about it.
40
+
41
+ Returns existing captions/subtitles only; it does not transcribe audio. Videos without
42
+ captions have nothing to return.
43
+
44
+ Args:
45
+ video: A YouTube URL (watch, youtu.be, shorts, embed, live) or an 11-character video ID.
46
+ languages: Preferred language codes in priority order. Defaults to ["en"].
47
+ include_timestamps: If true, prefix each line with [mm:ss] (or [h:mm:ss] past an hour).
48
+ translate_to: Optional ISO language code to translate the transcript into.
49
+
50
+ Returns:
51
+ The transcript as plain text.
52
+ """
53
+ return transcripts.get_transcript(
54
+ video,
55
+ tuple(languages) if languages else ("en",),
56
+ include_timestamps,
57
+ translate_to,
58
+ )
59
+
60
+
61
+ @mcp.tool()
62
+ def list_transcripts(video: str) -> TranscriptListing:
63
+ """List the transcripts available for a YouTube video.
64
+
65
+ Use this when get_transcript can't find your requested language. It reports each available
66
+ transcript (language, code, whether it's auto-generated, whether it's translatable) plus
67
+ the set of languages you can pass to get_transcript's translate_to.
68
+
69
+ Args:
70
+ video: A YouTube URL or an 11-character video ID.
71
+ """
72
+ return transcripts.list_transcripts(video)
73
+
74
+
75
+ def main() -> None:
76
+ """Console-script entry point.
77
+
78
+ Defaults to stdio, for clients that spawn the server themselves. Use ``--transport http``
79
+ to run a long-lived HTTP server instead, which is handy when the MCP client lives on a
80
+ different host than the server -- e.g. LM Studio on Windows connecting to this server
81
+ running in WSL2 at ``http://localhost:8000/mcp``.
82
+ """
83
+ parser = argparse.ArgumentParser(prog="youtube-context-mcp", description=main.__doc__)
84
+ parser.add_argument(
85
+ "--transport",
86
+ choices=["stdio", "http", "sse"],
87
+ default="stdio",
88
+ help="Transport to serve on (default: stdio).",
89
+ )
90
+ parser.add_argument(
91
+ "--host",
92
+ default="127.0.0.1",
93
+ help="Host to bind for http/sse (default: 127.0.0.1; use 0.0.0.0 to reach it from "
94
+ "Windows when running in WSL2).",
95
+ )
96
+ parser.add_argument(
97
+ "--port",
98
+ type=int,
99
+ default=8000,
100
+ help="Port to bind for http/sse (default: 8000).",
101
+ )
102
+ args = parser.parse_args()
103
+
104
+ if args.transport == "stdio":
105
+ mcp.run()
106
+ return
107
+
108
+ mcp.settings.host = args.host
109
+ mcp.settings.port = args.port
110
+ mcp.run(transport="streamable-http" if args.transport == "http" else "sse")
111
+
112
+
113
+ if __name__ == "__main__":
114
+ main()