hasdata-youtube-mcp 1.0.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,21 @@
1
+ ---
2
+ name: Something in the README is wrong
3
+ about: A tool table, a response sample or a documented behaviour does not match reality
4
+ labels: documentation
5
+ ---
6
+
7
+ **Where in the README**
8
+
9
+ Section or heading.
10
+
11
+ **What it says**
12
+
13
+ Quote the line.
14
+
15
+ **The call you made**
16
+
17
+ Tool name and arguments, or the equivalent REST URL with your key removed.
18
+
19
+ **What came back**
20
+
21
+ Trimmed response, with anything private removed.
@@ -0,0 +1,31 @@
1
+ # The tool contract is checked on a schedule as well as on push, because the upstream tool list
2
+ # can change without a single commit in this repository.
3
+ name: tool contract
4
+
5
+ on:
6
+ push:
7
+ branches: [main]
8
+ pull_request:
9
+ schedule:
10
+ - cron: '0 6 * * 1'
11
+ workflow_dispatch:
12
+
13
+ permissions:
14
+ contents: read
15
+
16
+ jobs:
17
+ contract:
18
+ runs-on: ubuntu-latest
19
+ timeout-minutes: 5
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ - uses: actions/setup-node@v4
23
+ with:
24
+ node-version: '22'
25
+ # Forks cannot read repository secrets. The suite skips its live checks when the key is
26
+ # absent, so a pull request from a fork stays green instead of failing for a reason the
27
+ # contributor cannot fix.
28
+ - name: Assert the tool list still matches the README
29
+ env:
30
+ HASDATA_API_KEY: ${{ secrets.HASDATA_API_KEY }}
31
+ run: npm test
@@ -0,0 +1,65 @@
1
+ # Publishes the npm and PyPI wrapper packages on a version tag, using OIDC
2
+ # trusted publishing. No NPM_TOKEN or PYPI_TOKEN is stored anywhere: GitHub
3
+ # mints a short-lived OIDC token per run, and npmjs.org / pypi.org accept it
4
+ # because this repo + workflow are configured as trusted publishers.
5
+ #
6
+ # One-time setup, done once per package on the registries (not in this repo):
7
+ # npmjs.org -> package settings -> Trusted Publisher -> GitHub Actions,
8
+ # repo HasData/youtube-mcp, workflow publish.yml
9
+ # pypi.org -> the hasdata org -> Publishing -> add a trusted publisher
10
+ # (pending publisher works before the first release),
11
+ # repo HasData/youtube-mcp, workflow publish.yml
12
+ #
13
+ # The MCP registry entry (com.hasdata/youtube) is NOT published here. It uses
14
+ # domain auth, which would need the namespace-wide Ed25519 key as a secret in
15
+ # every repo. That key stays off CI; the registry entry is published by hand
16
+ # when server.json changes, after the package versions below are live.
17
+ #
18
+ # Release: bump nothing by hand. Tag the commit `vX.Y.Z` and push the tag; the
19
+ # tag is the single source of the version and is written into both manifests.
20
+
21
+ name: publish
22
+
23
+ on:
24
+ push:
25
+ tags: ['v*.*.*']
26
+
27
+ permissions:
28
+ contents: read
29
+ id-token: write
30
+
31
+ jobs:
32
+ npm:
33
+ runs-on: ubuntu-latest
34
+ steps:
35
+ - uses: actions/checkout@v4
36
+ - uses: actions/setup-node@v4
37
+ with:
38
+ node-version: '22'
39
+ registry-url: 'https://registry.npmjs.org'
40
+ - name: Set version from the tag
41
+ run: npm version "${GITHUB_REF_NAME#v}" --no-git-tag-version --allow-same-version
42
+ - name: Publish to npm (OIDC, no token)
43
+ run: npm publish --access public
44
+
45
+ pypi:
46
+ runs-on: ubuntu-latest
47
+ steps:
48
+ - uses: actions/checkout@v4
49
+ - uses: actions/setup-python@v5
50
+ with:
51
+ python-version: '3.12'
52
+ - name: Set version from the tag
53
+ run: |
54
+ python - "${GITHUB_REF_NAME#v}" <<'PY'
55
+ import re, sys
56
+ v = sys.argv[1]
57
+ p = "pyproject.toml"
58
+ t = open(p, encoding="utf-8").read()
59
+ t = re.sub(r'(?m)^version = ".*"$', f'version = "{v}"', t, count=1)
60
+ open(p, "w", encoding="utf-8", newline="\n").write(t)
61
+ PY
62
+ - name: Build the wheel and sdist
63
+ run: pipx run build
64
+ - name: Publish to PyPI (OIDC, no token)
65
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,11 @@
1
+ node_modules/
2
+ package-lock.json
3
+ dist/
4
+ build/
5
+ *.egg-info/
6
+ __pycache__/
7
+ *.pyc
8
+ .env
9
+ .env.*
10
+ .DS_Store
11
+ *.log
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 HasData
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,506 @@
1
+ Metadata-Version: 2.5
2
+ Name: hasdata-youtube-mcp
3
+ Version: 1.0.0
4
+ Summary: MCP server for YouTube data through HasData's hosted API. No Google Cloud project, no YouTube Data API key.
5
+ Project-URL: Homepage, https://hasdata.com/apis/youtube-scraper-api
6
+ Project-URL: Repository, https://github.com/HasData/youtube-mcp
7
+ License: MIT
8
+ License-File: LICENSE
9
+ Keywords: hasdata,mcp,model-context-protocol,youtube
10
+ Requires-Python: >=3.10
11
+ Requires-Dist: mcp-proxy>=0.12.0
12
+ Requires-Dist: mcp<2,>=1.17
13
+ Description-Content-Type: text/markdown
14
+
15
+ # YouTube MCP Server
16
+
17
+ <!-- mcp-name: com.hasdata/youtube -->
18
+
19
+ A hosted Model Context Protocol (MCP) server that gives Claude, Cursor, Windsurf and any other MCP client four read-only YouTube tools. Search YouTube, read video and channel data, and pull transcripts, with no Google Cloud project and no YouTube Data API key.
20
+
21
+ ```
22
+ https://mcp.hasdata.com/api/mcp?apis=youtube
23
+ ```
24
+
25
+ [![tool contract](https://github.com/HasData/youtube-mcp/actions/workflows/contract.yml/badge.svg)](https://github.com/HasData/youtube-mcp/actions/workflows/contract.yml)
26
+ [![MCP](https://img.shields.io/badge/MCP-remote%20%7C%20streamable%20HTTP-6366f1?style=flat-square)](https://modelcontextprotocol.io)
27
+ [![Tools](https://img.shields.io/badge/tools-4-10b981?style=flat-square)](#tools)
28
+ [![License](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](LICENSE)
29
+
30
+ ## Contents
31
+
32
+ - [What you need](#what-you-need)
33
+ - [Quick start](#quick-start)
34
+ - [Example prompts](#example-prompts)
35
+ - [Tools](#tools)
36
+ - [Errors and failure paths](#errors-and-failure-paths)
37
+ - [Pricing, free tier and limits](#pricing-free-tier-and-limits)
38
+ - [Tool selection](#tool-selection)
39
+ - [How it compares](#how-it-compares)
40
+ - [FAQ](#faq)
41
+ - [HasData links](#hasdata-links)
42
+ - [Development](#development)
43
+ - [Contributing](#contributing)
44
+ - [License](#license)
45
+
46
+ ## What you need
47
+
48
+ An MCP client that speaks streamable HTTP with custom headers. A HasData API key from the [dashboard](https://app.hasdata.com/sign-up?utm_source=github&utm_medium=syndication&utm_campaign=youtube-mcp), free to create. Nothing else. This is a remote server. There is no package to install, no container to run and no Google account anywhere in the flow.
49
+
50
+ ## Quick start
51
+
52
+ The server URL is the same for every client. We run it hands-on in Claude Code and Claude Desktop. The other blocks follow each client's own documented format for a remote server.
53
+
54
+ | Field | Value |
55
+ | :--- | :--- |
56
+ | URL | `https://mcp.hasdata.com/api/mcp?apis=youtube` |
57
+ | Transport | HTTP, streamable |
58
+ | Auth header | `x-api-key: HASDATA_API_KEY` |
59
+
60
+ Clients with OAuth support can add the same URL as a connector and sign in without putting a key in a config file.
61
+
62
+ <details>
63
+ <summary><b>Claude Code</b></summary>
64
+
65
+ ```bash
66
+ claude mcp add --transport http youtube "https://mcp.hasdata.com/api/mcp?apis=youtube" \
67
+ --header "x-api-key: HASDATA_API_KEY"
68
+ ```
69
+
70
+ </details>
71
+
72
+ <details>
73
+ <summary><b>Claude Desktop</b></summary>
74
+
75
+ Settings, then Connectors, then Add custom connector, then paste `https://mcp.hasdata.com/api/mcp?apis=youtube` and sign in.
76
+
77
+ For the config-file route, Claude Desktop loads only local (stdio) servers, so a remote server is reached through the `mcp-remote` bridge, which needs Node. Add this to `claude_desktop_config.json`:
78
+
79
+ ```json
80
+ {
81
+ "mcpServers": {
82
+ "youtube": {
83
+ "command": "npx",
84
+ "args": [
85
+ "-y",
86
+ "mcp-remote",
87
+ "https://mcp.hasdata.com/api/mcp?apis=youtube",
88
+ "--header",
89
+ "x-api-key:HASDATA_API_KEY"
90
+ ]
91
+ }
92
+ }
93
+ }
94
+ ```
95
+
96
+ The `x-api-key:` value carries no space after the colon. Claude Desktop passes the argument without a shell, and a space splits the header.
97
+
98
+ </details>
99
+
100
+ <details>
101
+ <summary><b>Cursor</b></summary>
102
+
103
+ `~/.cursor/mcp.json` for every project, or `.cursor/mcp.json` for one:
104
+
105
+ ```json
106
+ {
107
+ "mcpServers": {
108
+ "youtube": {
109
+ "url": "https://mcp.hasdata.com/api/mcp?apis=youtube",
110
+ "headers": { "x-api-key": "HASDATA_API_KEY" }
111
+ }
112
+ }
113
+ }
114
+ ```
115
+
116
+ </details>
117
+
118
+ <details>
119
+ <summary><b>Windsurf</b></summary>
120
+
121
+ `~/.codeium/windsurf/mcp_config.json`. Windsurf calls the field `serverUrl`, not `url`:
122
+
123
+ ```json
124
+ {
125
+ "mcpServers": {
126
+ "youtube": {
127
+ "serverUrl": "https://mcp.hasdata.com/api/mcp?apis=youtube",
128
+ "headers": { "x-api-key": "HASDATA_API_KEY" }
129
+ }
130
+ }
131
+ }
132
+ ```
133
+
134
+ </details>
135
+
136
+ <details>
137
+ <summary><b>Cline</b></summary>
138
+
139
+ ```json
140
+ {
141
+ "mcpServers": {
142
+ "youtube": {
143
+ "url": "https://mcp.hasdata.com/api/mcp?apis=youtube",
144
+ "type": "streamableHttp",
145
+ "headers": { "x-api-key": "HASDATA_API_KEY" },
146
+ "disabled": false
147
+ }
148
+ }
149
+ }
150
+ ```
151
+
152
+ </details>
153
+
154
+ <details>
155
+ <summary><b>VS Code</b></summary>
156
+
157
+ `.vscode/mcp.json` in the workspace:
158
+
159
+ ```json
160
+ {
161
+ "servers": {
162
+ "youtube": {
163
+ "type": "http",
164
+ "url": "https://mcp.hasdata.com/api/mcp?apis=youtube",
165
+ "headers": { "x-api-key": "HASDATA_API_KEY" }
166
+ }
167
+ }
168
+ }
169
+ ```
170
+
171
+ </details>
172
+
173
+ <details>
174
+ <summary><b>Codex CLI</b></summary>
175
+
176
+ `~/.codex/config.toml`:
177
+
178
+ ```toml
179
+ [mcp_servers.youtube]
180
+ url = "https://mcp.hasdata.com/api/mcp?apis=youtube"
181
+
182
+ [mcp_servers.youtube.headers]
183
+ "x-api-key" = "HASDATA_API_KEY"
184
+ ```
185
+
186
+ </details>
187
+
188
+ <details>
189
+ <summary><b>Gemini CLI</b></summary>
190
+
191
+ `~/.gemini/settings.json`:
192
+
193
+ ```json
194
+ {
195
+ "mcpServers": {
196
+ "youtube": {
197
+ "httpUrl": "https://mcp.hasdata.com/api/mcp?apis=youtube",
198
+ "headers": { "x-api-key": "HASDATA_API_KEY" }
199
+ }
200
+ }
201
+ }
202
+ ```
203
+
204
+ </details>
205
+
206
+ ## Example prompts
207
+
208
+ Prompts, not code. Paste one in and the agent picks the tool itself. Each is annotated with the calls it takes, because in MCP the model decides how many calls to make and every successful call costs 10 credits.
209
+
210
+ > Find the ten most viewed videos about the Model Context Protocol from the last month, then pull the transcript of the top one and give me the three claims it makes about tool calling.
211
+
212
+ *Two calls, 20 credits.*
213
+
214
+ > Take the channel @GoogleDevelopers. List the tabs it publishes, then summarize the last five uploads and tell me which topics repeat.
215
+
216
+ *Two calls, 20 credits. Reading a tab you have not seen takes a second call, because the tab list arrives inside the first response.*
217
+
218
+ > Take this video id, dQw4w9WgXcQ. Get its stats, then check which of its related videos come from the same channel.
219
+
220
+ *One call, 10 credits. Related videos ride along in the same response.*
221
+
222
+ > Search YouTube for "web scraping tutorial", sorted by upload date, videos under four minutes only, and give me the chapter titles of each result that has them.
223
+
224
+ *One call, 10 credits.*
225
+
226
+ > Pull the German transcript of this video if one exists, and tell me which languages it is available in.
227
+
228
+ *One call, 10 credits.*
229
+
230
+ Search takes YouTube's own filter tokens, and an agent narrows by duration, upload date and content type without post-processing. Transcripts arrive with the list of available language tracks, which lets the agent pick one without guessing.
231
+
232
+ Paging costs a call each time. A research prompt that searches, pages twice, then pulls three transcripts is six calls and 60 credits. The trial goes further on narrow questions than on open-ended crawls.
233
+
234
+ ## Tools
235
+
236
+ Four tools, all read-only. Samples below are trimmed from real calls, and the numbers in them move as YouTube updates. Read them as shapes. Each tool name links to its endpoint reference, which carries the full field list.
237
+
238
+ The samples are the payload, not the whole response. A `tools/call` result carries one text block, and that text is itself JSON holding `url`, `status`, `text` and `json`, with the scraped data under `json`. From a raw JSON-RPC response the path is `result.content[0].text`, parsed, then `.json`. A chat client unwraps that for you and code talking to the endpoint directly does not.
239
+
240
+ ### Get YouTube search results
241
+
242
+ [`hasdata_youtube_search_getYoutubeSearchResults`](https://docs.hasdata.com/apis/youtube/search?utm_source=github&utm_medium=syndication&utm_campaign=youtube-mcp)
243
+
244
+ Searches YouTube and returns the whole results page, split by result type.
245
+
246
+ | Parameter | Type | Required | Notes |
247
+ | :--- | :--- | :--- | :--- |
248
+ | `q` | string | yes | Free-text query, exactly as a user would type it |
249
+ | `sortBy` | string | | `relevance` by default, plus `date`, `views`, `rating` and `popularity` |
250
+ | `date` | string | | Upload window relative to now |
251
+ | `length` | string | | Duration bucket, for example `under4` |
252
+ | `videoType` | string | | Restrict to one content type |
253
+ | `filters__` | array | | Feature flags, combinable |
254
+ | `sp` | string | | Raw YouTube `sp` token copied from a search URL. Overrides `sortBy`, `date`, `videoType`, `length` and `filters__` with no warning, so leave those empty when you pass a token |
255
+ | `paginationToken` | string | | The `pagination.nextPageToken` from the previous response |
256
+ | `gl` / `hl` / `deviceType` | string | | Two-letter country and language codes, and device |
257
+
258
+ A results page is split across `videoResults`, `shortsResults`, `inlineShortsResults`, `playlistResults`, `channelResults` and `shelves`, with paid placements in `adsResults` and `sponsoredResults`. Which blocks appear depends on the query, and a block with nothing to report is absent, not empty. Test for the key before iterating. `searchInformation` carries the total and `pagination.nextPageToken` is what you feed back as `paginationToken`. Ads never mix into the organic arrays, though there are two of them to skip.
259
+
260
+ ```json
261
+ {
262
+ "positionOnPage": 1,
263
+ "videoId": "GuTcle5edjk",
264
+ "title": "you need to learn MCP RIGHT NOW!! (Model Context Protocol)",
265
+ "viewsOriginal": "1.6M views",
266
+ "views": 1653824,
267
+ "length": "38:40",
268
+ "publishedDate": "11 months ago",
269
+ "extensions": ["4K"],
270
+ "chapters": [
271
+ { "title": "Intro", "time": "0:00" },
272
+ { "title": "Problem: LLMs Suck at Accessing Code", "time": "0:40" }
273
+ ],
274
+ "channel": { "name": "NetworkChuck", "verified": true }
275
+ }
276
+ ```
277
+
278
+ Two things there earn a mention. `views` is a parsed integer next to the `1.6M views` display string and needs no suffix parser. And `chapters` come back inside search results, not only on the video itself, though only some videos carry them.
279
+
280
+ The [search endpoint reference](https://docs.hasdata.com/apis/youtube/search?utm_source=github&utm_medium=syndication&utm_campaign=youtube-mcp) lists every `sp` and `filters__` token the endpoint accepts.
281
+
282
+ ### Get YouTube video data
283
+
284
+ [`hasdata_youtube_video_getYoutubeVideo`](https://docs.hasdata.com/apis/youtube/video?utm_source=github&utm_medium=syndication&utm_campaign=youtube-mcp)
285
+
286
+ One video by id.
287
+
288
+ | Parameter | Type | Required | Notes |
289
+ | :--- | :--- | :--- | :--- |
290
+ | `v` | string | yes | The 11-character video id from `v=` |
291
+ | `gl` / `hl` / `deviceType` | string | | Two-letter country and language codes, and device |
292
+
293
+ Returns `title`, `thumbnail`, `channel`, `publishedDate`, `lengthSeconds`, `category`, `isFamilySafe` and `isUnlisted`, plus the `relatedVideos`, `endScreenVideos`, `keywords`, `captions`, `music` and `socialLinks` arrays. `description` is an object holding the full text in `content` and a `links` array where every link and hashtag carries `startIndex`, `length`, `text` and `url`. The `text` field holds the link as the author wrote it and `url` holds YouTube's redirect wrapper, which matters if you are pulling sponsor or affiliate destinations out of descriptions.
294
+
295
+ > Read the parsed field by name per tool before you copy the sample below. Search and channel results put the parsed number in `views` and the display string in `viewsOriginal`. This response inverts it, keeping the string in `views` and the number in `extractedViews`, and the same inversion applies to `likes` and `subscribers`. Get it wrong and `item.views > 100000` compares a string here without ever throwing.
296
+
297
+ ```json
298
+ {
299
+ "title": "Rick Astley - Never Gonna Give You Up (Official Video) (4K Remaster)",
300
+ "views": "1,806,075,152 views",
301
+ "extractedViews": 1806075152,
302
+ "likes": "19M",
303
+ "extractedLikes": 19344370,
304
+ "publishedDate": "Oct 24, 2009",
305
+ "lengthSeconds": 214,
306
+ "category": "Music",
307
+ "channel": { "name": "Rick Astley", "subscribers": "4.53M subscribers", "extractedSubscribers": 4530000 }
308
+ }
309
+ ```
310
+
311
+ ### Get YouTube channel data
312
+
313
+ [`hasdata_youtube_channel_getYoutubeChannel`](https://docs.hasdata.com/apis/youtube/channel?utm_source=github&utm_medium=syndication&utm_campaign=youtube-mcp)
314
+
315
+ A channel by id or handle, one tab at a time.
316
+
317
+ | Parameter | Type | Required | Notes |
318
+ | :--- | :--- | :--- | :--- |
319
+ | `channelId` | string | yes | Canonical `UC…` id or a `@handle` |
320
+ | `tab` | string | | `featured` by default, plus `videos`, `shorts`, `streams`, `playlists`, `posts`, `community`, `podcasts`, `releases`, `about` and `store`. Take a value from this list, not from `availableTabs` in the response |
321
+ | `paginationToken` | string | | Token from the previous response |
322
+ | `gl` / `hl` / `deviceType` | string | | Two-letter country and language codes, and device |
323
+
324
+ Returns `channelInfo`, `featuredVideo` and `sections` on the default tab. Other tabs return their own shape. `channelInfo` carries the handle, avatar, banner, description, channel keywords and the channel's `rssUrl`, enough to keep watching a channel without polling it.
325
+
326
+ > The `availableTabs` array in the sample below holds display labels, and they are not the values `tab` accepts. `Home`, `Live`, `Courses` and `Search` map to no parameter value at all, and the rest need lowercasing. An agent that reads the list and walks each entry fails on the first one.
327
+
328
+ ```json
329
+ {
330
+ "channelInfo": {
331
+ "channelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw",
332
+ "name": "Google for Developers",
333
+ "handle": "@GoogleDevelopers",
334
+ "rssUrl": "https://www.youtube.com/feeds/videos.xml?channel_id=UC_x5XG1OV2P6uZZ5FSM9Ttw",
335
+ "isFamilySafe": true,
336
+ "availableTabs": ["Home", "Videos", "Shorts", "Live", "Courses", "Playlists", "Posts", "Search"]
337
+ }
338
+ }
339
+ ```
340
+
341
+ ### Get YouTube video transcript
342
+
343
+ [`hasdata_youtube_transcript_getYoutubeTranscript`](https://docs.hasdata.com/apis/youtube/transcript?utm_source=github&utm_medium=syndication&utm_campaign=youtube-mcp)
344
+
345
+ The timed transcript of a video.
346
+
347
+ | Parameter | Type | Required | Notes |
348
+ | :--- | :--- | :--- | :--- |
349
+ | `v` | string | yes | The 11-character video id |
350
+ | `languageCode` | string | | BCP-47 code of the track you want |
351
+ | `type` | string | | Set to `asr` for the auto-generated track |
352
+
353
+ > Check `selected` in the response before you trust the language. Asking for a `languageCode` the video does not carry neither fails nor returns empty, it quietly falls back to the default track. Every entry in the list carries `languageName` and `languageCode`, and one language can appear twice, once human-authored and once with `type` set to `asr`.
354
+
355
+ ```json
356
+ {
357
+ "transcript": [
358
+ { "startMs": 320, "endMs": 18800, "snippet": "[Music]", "startTimeText": "0:00" },
359
+ { "startMs": 18800, "endMs": 21800, "snippet": "We're no strangers to", "startTimeText": "0:18" }
360
+ ],
361
+ "availableTranscripts": [
362
+ { "languageName": "English", "languageCode": "en" },
363
+ { "languageName": "English", "languageCode": "en", "type": "asr", "selected": true },
364
+ { "languageName": "German (Germany)", "languageCode": "de-DE" },
365
+ { "languageName": "Japanese", "languageCode": "ja" }
366
+ ]
367
+ }
368
+ ```
369
+
370
+ ## Errors and failure paths
371
+
372
+ Your client almost never sees an HTTP error code from a tool call. The MCP layer answers 200 and puts the failure inside the result, with `isError` set to `true` and the reason as text. The agent reads a message where you might expect a status line.
373
+
374
+ **A wrong key surfaces as tool output, not as a failed connection.** `tools/list` accepts any non-empty key and returns all four tools, so the client completes its handshake and shows green. The first tool call then comes back with `isError: true` and the text `HasData API error: 401 Unauthorized`. Watch for that string, because nothing earlier in the flow reports the problem.
375
+
376
+ **A missing key is the one real HTTP error.** Authorization runs before any tool, and the connection itself fails with 401. CORS headers are present, and a browser client reads the status and not an opaque network failure.
377
+
378
+ **An argument that breaks a tool's schema is rejected before it becomes a scrape.** The server answers with `isError: true` and the text `MCP error -32602: Input validation error`, naming the offending field. Nothing is fetched and nothing is charged. The message names the field but not the accepted values, so the parameter tables above are the reference.
379
+
380
+ **A call that succeeds and finds nothing is the case that trips people up.** It arrives as an ordinary result with `requestMetadata.status` set to `ok` and the data key simply missing. Nothing in the body says the result was empty. Test for the field you need, not for an error.
381
+
382
+ **An identifier the platform rejects returns 400** with `requestMetadata.status` set to `error`. A channel handle that does not exist is the usual way to see this.
383
+
384
+ Results that carry data also carry a `requestMetadata.id` worth quoting in support.
385
+
386
+ ## Pricing, free tier and limits
387
+
388
+ Every YouTube tool costs **10 credits per successful call**. Response size does not change the price. A full page of search results costs the same as a page with one video.
389
+
390
+ The free trial is **1,000 credits over 30 days with no card**, which is 100 YouTube calls. After that an active account keeps getting 100 credits topped up each day whenever its balance drops below 100, so a low-volume agent runs on the free tier indefinitely.
391
+
392
+ Paid plans start at **$49 a month** for 200,000 credits, which is 20,000 calls. The unit price falls with volume, from **$2.45 per 1,000 calls** on the entry plan to **$0.99** on Business, **$0.83** on Growth and **$0.75** on the largest [high-volume plans](https://hasdata.com/prices?utm_source=github&utm_medium=syndication&utm_campaign=youtube-mcp).
393
+
394
+ Your plan also sets concurrency. The free trial allows 1 request at a time, Startup 15, Business 30, Growth 50, and the high-volume plans run from 200 to 1,500. Handle the overflow case defensively in anything unattended, because an agent that fans out will reach the ceiling before you do.
395
+
396
+ A request that comes back non-200 is not billed. A successful call that finds nothing is still a call.
397
+
398
+ ## Tool selection
399
+
400
+ The `apis` query parameter decides which tools your agent sees. Fewer tools means less context spent on tool definitions, and fewer chances for the model to reach for the wrong one.
401
+
402
+ ```
403
+ ?apis=youtube the four tools in this repo
404
+ ?apis=youtube,google_serp add Google search
405
+ ?apis=youtube,tiktok,instagram a social research bundle
406
+ ```
407
+
408
+ The parameter takes provider names like `youtube` and individual API names like `google_maps_search`. Misspelled names are ignored. If every name is wrong the request fails with 400, and the body lists both what it did not recognise and every valid value. Drop the parameter and the same endpoint exposes all 57 HasData tools.
409
+
410
+ ## How it compares
411
+
412
+ Against the official **YouTube Data API v3**:
413
+
414
+ | | YouTube Data API v3 | This server |
415
+ | :--- | :--- | :--- |
416
+ | Setup | Google Cloud project and an API key | One key and one URL |
417
+ | Search allowance | "default quota allocation of 100 `search.list` calls" a day, per [Google's getting started guide](https://developers.google.com/youtube/v3/getting-started) | Your plan's credits, 10 per call |
418
+ | Transcripts of videos you do not own | `captions.download` "requires the user to have permission to edit the video", per [Google's reference](https://developers.google.com/youtube/v3/docs/captions/download) | Yes, with the language list |
419
+ | Chapters in search results | No | Yes |
420
+ | Views and likes in search results | Absent, and a second `videos.list` call returns them as strings | Display string and integer in the same response |
421
+ | Cost | Free inside the daily quota | Paid past the trial, 10 credits a call |
422
+ | Writes and private data | Uploads, playlists, comments and your own analytics over OAuth | Read-only, public data only |
423
+
424
+ The last two rows matter. If the daily quota covers your volume and you own the channel you are querying, the official API is the cheaper answer and you should take it.
425
+
426
+ Most other YouTube MCP servers do transcripts only. This one also searches, reads videos with their engagement numbers, and walks channel tabs, so an agent runs a whole research pass without a second server.
427
+
428
+ **What this server does not do.** No comments, no channel management, no uploads, no analytics, no private data. It reads what a signed-out visitor can see.
429
+
430
+ ## FAQ
431
+
432
+ ### Is there an official YouTube MCP server?
433
+
434
+ Google does not publish one. YouTube has no first-party MCP server. Every option is built by somebody else, either around the YouTube Data API v3 or around the public pages. This one is maintained by HasData and reads public pages, which is why it needs no Google credentials.
435
+
436
+ ### What is a YouTube MCP server?
437
+
438
+ A server that exposes YouTube data as tools an AI client can call. The client sends a tool call over the Model Context Protocol, the server fetches the data and returns structured JSON, and the model works with the result and never sees a page of HTML. This one exposes four tools and runs remotely. The client connects to a URL and starts no local process.
439
+
440
+ ### Do I need a YouTube API key or a Google Cloud project?
441
+
442
+ No. The only credential is your HasData key. There is no Google Cloud project to create, no quota form to fill in and no OAuth consent screen, because the tools read public YouTube pages and not the YouTube Data API.
443
+
444
+ ### Do I need to host or run anything?
445
+
446
+ No. This is a remote MCP server on streamable HTTP. Nothing to install, no container to keep warm, no process to restart.
447
+
448
+ ### Is the data live or cached?
449
+
450
+ Live. Each call fetches the page at request time and carries its own `requestMetadata.id`. Two identical calls are two separate fetches and not a replay of a stored copy. Counters like views and likes track the page, so they move as the page moves.
451
+
452
+ ### What happens when YouTube changes its layout?
453
+
454
+ Nothing on your side. We track the changes and keep the response schema stable, so field names and types stay put. A field with no value is absent from the item, not present and null. Read optional fields with a default.
455
+
456
+ ### Can I use this together with other HasData APIs?
457
+
458
+ Yes. The `apis` parameter takes a list, and `?apis=youtube,google_serp` gives your agent the four YouTube tools plus Google search. [Drop the parameter](#tool-selection) and you get everything.
459
+
460
+ ### Can I get a transcript for any video?
461
+
462
+ Only where the video has one, and `availableTranscripts` tells you what exists before you ask.
463
+
464
+ ### Can I sign in with OAuth instead of pasting a key?
465
+
466
+ Yes, in clients that support it. Claude Desktop and Cursor can add the endpoint as a connector and sign in. Unattended agents and scripts use the `x-api-key` header.
467
+
468
+ ### Compliance and personal data
469
+
470
+ HasData accesses publicly available data only. A platform's terms may restrict automated access, and you are responsible for your own compliance. Where the data you collect includes personal information, make sure you have a lawful basis for it under GDPR, CCPA or the equivalent rules in your jurisdiction.
471
+
472
+ ## HasData links
473
+
474
+ | | |
475
+ | :--- | :--- |
476
+ | Product page and request builder | [YouTube Scraper API](https://hasdata.com/apis/youtube-scraper-api?utm_source=github&utm_medium=syndication&utm_campaign=youtube-mcp) |
477
+ | Server documentation | [MCP server docs](https://docs.hasdata.com/mcp-server?utm_source=github&utm_medium=syndication&utm_campaign=youtube-mcp) |
478
+ | All 57 tools in one server | [HasData/hasdata-mcp](https://github.com/HasData/hasdata-mcp) |
479
+ | Client walkthroughs | [MCP clients and integrations](https://hasdata.com/integrations/mcp?utm_source=github&utm_medium=syndication&utm_campaign=youtube-mcp) |
480
+ | Everything else we scrape | [YouTube Scraper API and 54 more](https://hasdata.com/apis/?utm_source=github&utm_medium=syndication&utm_campaign=youtube-mcp) |
481
+ | Plans and credit costs | [Plans and credit costs](https://hasdata.com/prices?utm_source=github&utm_medium=syndication&utm_campaign=youtube-mcp) |
482
+ | Keys and usage | [HasData dashboard](https://app.hasdata.com?utm_source=github&utm_medium=syndication&utm_campaign=youtube-mcp) |
483
+
484
+ ## Development
485
+
486
+ This repository is configuration and documentation for a remote server. There is no build step and nothing to containerize.
487
+
488
+ The tests in `test/` assert the tool contract, the part that can break without a commit here. They check that `?apis=youtube` returns exactly four tools, that every tool still declares its required parameter, that no name changed, and that the key in use is actually accepted. That last check calls a tool for real and costs 10 credits, which is the price of a canary that can fail for the right reason.
489
+
490
+ ```bash
491
+ # macOS and Linux
492
+ HASDATA_API_KEY=your_key_here npm test
493
+
494
+ # Windows PowerShell
495
+ $env:HASDATA_API_KEY="your_key_here"; npm test
496
+ ```
497
+
498
+ The same suite runs in CI on every push and once a week on a schedule, because the upstream tool list can change without anyone touching this repository. A failure means the tool list moved, the key stopped working, or the endpoint was unreachable, and the assertion message says which.
499
+
500
+ ## Contributing
501
+
502
+ Corrections to the tool tables and the response samples are the most useful contribution, because those are the parts that drift. Include the call you made and the response you got. Pull requests from forks run the suite without a key, and the live checks skip instead of going red.
503
+
504
+ ## License
505
+
506
+ MIT. See [LICENSE](LICENSE).