@alexspalato/youtube-transcript-mcp 1.0.0
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.
- package/LICENSE +21 -0
- package/README.md +75 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +168 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 alexadark
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# YouTube Transcript MCP
|
|
2
|
+
|
|
3
|
+
Get YouTube video transcripts directly in Claude. Just share a YouTube link - no technical syntax needed.
|
|
4
|
+
|
|
5
|
+
## What It Does
|
|
6
|
+
|
|
7
|
+
Share any YouTube URL with Claude, and this MCP lets Claude fetch the transcript for you. Claude will naturally ask how you'd like to view it:
|
|
8
|
+
|
|
9
|
+
- **Readable text** - Clean paragraphs, perfect for reading and analysis
|
|
10
|
+
- **With timestamps** - Each line shows when it was said (great for referencing specific moments)
|
|
11
|
+
- **Structured data** - JSON format with full metadata (for developers)
|
|
12
|
+
|
|
13
|
+
Works with any YouTube video that has captions, in any language.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
**One command:**
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install -g @alexspalato/youtube-transcript-mcp
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Setup
|
|
24
|
+
|
|
25
|
+
### For Claude Code
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
claude mcp add youtube-transcript
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Restart Claude Code and you're done!
|
|
32
|
+
|
|
33
|
+
### For Claude Desktop
|
|
34
|
+
|
|
35
|
+
1. Open `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
36
|
+
2. Add this:
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"mcpServers": {
|
|
41
|
+
"youtube-transcript": {
|
|
42
|
+
"command": "npx",
|
|
43
|
+
"args": ["-y", "@alexspalato/youtube-transcript-mcp"]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
3. Restart Claude Desktop
|
|
50
|
+
|
|
51
|
+
## How to Use
|
|
52
|
+
|
|
53
|
+
Just chat naturally with Claude:
|
|
54
|
+
|
|
55
|
+
> "Can you get the transcript from https://youtu.be/dQw4w9WgXcQ?"
|
|
56
|
+
|
|
57
|
+
> "Summarize this video: https://youtu.be/eT_6uaHNlk8"
|
|
58
|
+
|
|
59
|
+
> "Get me the transcript with timestamps from this tutorial..."
|
|
60
|
+
|
|
61
|
+
Claude will handle everything - fetching the transcript, asking about your format preference, and displaying it clearly.
|
|
62
|
+
|
|
63
|
+
### Multiple Languages
|
|
64
|
+
|
|
65
|
+
Works with any language that has captions:
|
|
66
|
+
|
|
67
|
+
> "Get the Spanish transcript from this video"
|
|
68
|
+
|
|
69
|
+
> "Show me this video's transcript in French"
|
|
70
|
+
|
|
71
|
+
Common language codes: `en`, `es`, `fr`, `de`, `ja`, `ko`, `zh`, `pt`, `ru`, `ar`, `hi`
|
|
72
|
+
|
|
73
|
+
## License
|
|
74
|
+
|
|
75
|
+
MIT
|
package/build/index.d.ts
ADDED
package/build/index.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { fetchTranscript } from "youtube-transcript-plus";
|
|
6
|
+
/**
|
|
7
|
+
* Formats seconds into HH:MM:SS or MM:SS format
|
|
8
|
+
*/
|
|
9
|
+
function formatTimestamp(seconds) {
|
|
10
|
+
const hours = Math.floor(seconds / 3600);
|
|
11
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
12
|
+
const secs = Math.floor(seconds % 60);
|
|
13
|
+
if (hours > 0) {
|
|
14
|
+
return `${hours.toString().padStart(2, "0")}:${minutes
|
|
15
|
+
.toString()
|
|
16
|
+
.padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
|
|
17
|
+
}
|
|
18
|
+
return `${minutes.toString().padStart(2, "0")}:${secs
|
|
19
|
+
.toString()
|
|
20
|
+
.padStart(2, "0")}`;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Extracts video ID from YouTube URL or returns the input if it's already an ID
|
|
24
|
+
*/
|
|
25
|
+
function extractVideoId(urlOrId) {
|
|
26
|
+
// If it's already just an ID (no URL patterns), return it
|
|
27
|
+
if (!/[/:.]/.test(urlOrId)) {
|
|
28
|
+
return urlOrId;
|
|
29
|
+
}
|
|
30
|
+
// Handle various YouTube URL formats
|
|
31
|
+
const patterns = [
|
|
32
|
+
/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\?\/]+)/,
|
|
33
|
+
/youtube\.com\/shorts\/([^&\?\/]+)/,
|
|
34
|
+
];
|
|
35
|
+
for (const pattern of patterns) {
|
|
36
|
+
const match = urlOrId.match(pattern);
|
|
37
|
+
if (match) {
|
|
38
|
+
return match[1];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// If no pattern matched, assume it's an ID
|
|
42
|
+
return urlOrId;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Formats transcript segments into plain text paragraphs
|
|
46
|
+
*/
|
|
47
|
+
function formatPlainText(segments) {
|
|
48
|
+
return segments.map((segment) => segment.text.trim()).join(" ");
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Formats transcript segments with timestamps
|
|
52
|
+
*/
|
|
53
|
+
function formatTimestamped(segments) {
|
|
54
|
+
return segments
|
|
55
|
+
.map((segment) => {
|
|
56
|
+
const timestamp = formatTimestamp(segment.offset);
|
|
57
|
+
return `[${timestamp}] ${segment.text.trim()}`;
|
|
58
|
+
})
|
|
59
|
+
.join("\n");
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Formats transcript segments as JSON
|
|
63
|
+
*/
|
|
64
|
+
function formatJson(segments) {
|
|
65
|
+
const formatted = segments.map((segment) => ({
|
|
66
|
+
timestamp: formatTimestamp(segment.offset),
|
|
67
|
+
offset: segment.offset,
|
|
68
|
+
duration: segment.duration,
|
|
69
|
+
text: segment.text.trim(),
|
|
70
|
+
}));
|
|
71
|
+
return JSON.stringify(formatted, null, 2);
|
|
72
|
+
}
|
|
73
|
+
const server = new Server({
|
|
74
|
+
name: "youtube-transcript-mcp",
|
|
75
|
+
version: "1.0.0",
|
|
76
|
+
}, {
|
|
77
|
+
capabilities: {
|
|
78
|
+
tools: {},
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
82
|
+
return {
|
|
83
|
+
tools: [
|
|
84
|
+
{
|
|
85
|
+
name: "get_transcript",
|
|
86
|
+
description: "Fetches YouTube video transcripts. When a user shares a YouTube URL, use this tool to retrieve the transcript. If the user doesn't specify a format preference, ask them how they'd like to view it: as readable text (plain), with timestamps for referencing specific moments (timestamped), or as structured data (json).",
|
|
87
|
+
inputSchema: {
|
|
88
|
+
type: "object",
|
|
89
|
+
properties: {
|
|
90
|
+
url: {
|
|
91
|
+
type: "string",
|
|
92
|
+
description: "YouTube video URL or video ID",
|
|
93
|
+
},
|
|
94
|
+
lang: {
|
|
95
|
+
type: "string",
|
|
96
|
+
description: "Language code for transcript (e.g., 'en', 'es', 'fr'). Defaults to 'en'.",
|
|
97
|
+
default: "en",
|
|
98
|
+
},
|
|
99
|
+
format: {
|
|
100
|
+
type: "string",
|
|
101
|
+
enum: ["plain", "timestamped", "json"],
|
|
102
|
+
description: "Output format: 'plain' for readable text (default), 'timestamped' for text with [00:00] timestamps, 'json' for structured data with metadata",
|
|
103
|
+
default: "plain",
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
required: ["url"],
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
};
|
|
111
|
+
});
|
|
112
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
113
|
+
if (request.params.name !== "get_transcript") {
|
|
114
|
+
throw new Error(`Unknown tool: ${request.params.name}`);
|
|
115
|
+
}
|
|
116
|
+
const url = String(request.params.arguments?.url || "");
|
|
117
|
+
const lang = String(request.params.arguments?.lang || "en");
|
|
118
|
+
const format = (request.params.arguments?.format || "plain");
|
|
119
|
+
if (!url) {
|
|
120
|
+
throw new Error("URL parameter is required");
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
const videoId = extractVideoId(url);
|
|
124
|
+
const transcript = await fetchTranscript(videoId, { lang });
|
|
125
|
+
let formattedText;
|
|
126
|
+
switch (format) {
|
|
127
|
+
case "timestamped":
|
|
128
|
+
formattedText = formatTimestamped(transcript);
|
|
129
|
+
break;
|
|
130
|
+
case "json":
|
|
131
|
+
formattedText = formatJson(transcript);
|
|
132
|
+
break;
|
|
133
|
+
case "plain":
|
|
134
|
+
default:
|
|
135
|
+
formattedText = formatPlainText(transcript);
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
content: [
|
|
140
|
+
{
|
|
141
|
+
type: "text",
|
|
142
|
+
text: formattedText,
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
149
|
+
return {
|
|
150
|
+
content: [
|
|
151
|
+
{
|
|
152
|
+
type: "text",
|
|
153
|
+
text: `Error fetching transcript: ${errorMessage}`,
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
isError: true,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
async function main() {
|
|
161
|
+
const transport = new StdioServerTransport();
|
|
162
|
+
await server.connect(transport);
|
|
163
|
+
console.error("YouTube Transcript MCP Server running on stdio");
|
|
164
|
+
}
|
|
165
|
+
main().catch((error) => {
|
|
166
|
+
console.error("Server error:", error);
|
|
167
|
+
process.exit(1);
|
|
168
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@alexspalato/youtube-transcript-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Get YouTube transcripts in Claude with natural language - no technical syntax needed",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "build/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"youtube-transcript-mcp": "build/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"build",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18.0.0"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc",
|
|
20
|
+
"watch": "tsc --watch",
|
|
21
|
+
"prepare": "npm run build"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"mcp",
|
|
25
|
+
"model-context-protocol",
|
|
26
|
+
"youtube",
|
|
27
|
+
"transcript",
|
|
28
|
+
"claude",
|
|
29
|
+
"anthropic",
|
|
30
|
+
"ai",
|
|
31
|
+
"video",
|
|
32
|
+
"captions"
|
|
33
|
+
],
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/alexadark/youtube-transcript-mcp.git"
|
|
37
|
+
},
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/alexadark/youtube-transcript-mcp/issues"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/alexadark/youtube-transcript-mcp#readme",
|
|
42
|
+
"author": "alexadark",
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
46
|
+
"youtube-transcript-plus": "^1.0.0"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/node": "^22.10.2",
|
|
50
|
+
"typescript": "^5.7.2"
|
|
51
|
+
}
|
|
52
|
+
}
|