@thenavidm/youtube-mcp-cli 2.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 +877 -0
- package/SKILL.md +162 -0
- package/dist/accounts/store.d.ts +41 -0
- package/dist/accounts/store.js +110 -0
- package/dist/accounts/store.js.map +1 -0
- package/dist/auth.d.ts +14 -0
- package/dist/auth.js +206 -0
- package/dist/auth.js.map +1 -0
- package/dist/cli.d.ts +55 -0
- package/dist/cli.js +439 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +42 -0
- package/dist/config.js +130 -0
- package/dist/config.js.map +1 -0
- package/dist/doctor.d.ts +7 -0
- package/dist/doctor.js +78 -0
- package/dist/doctor.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +134 -0
- package/dist/index.js.map +1 -0
- package/dist/safety.d.ts +50 -0
- package/dist/safety.js +84 -0
- package/dist/safety.js.map +1 -0
- package/dist/server.d.ts +10 -0
- package/dist/server.js +28 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/account.d.ts +10 -0
- package/dist/tools/account.js +222 -0
- package/dist/tools/account.js.map +1 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.js +12 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/kit.d.ts +57 -0
- package/dist/tools/kit.js +78 -0
- package/dist/tools/kit.js.map +1 -0
- package/dist/tools/research.d.ts +16 -0
- package/dist/tools/research.js +232 -0
- package/dist/tools/research.js.map +1 -0
- package/dist/tools/transcripts.d.ts +9 -0
- package/dist/tools/transcripts.js +123 -0
- package/dist/tools/transcripts.js.map +1 -0
- package/dist/tools/types.d.ts +6 -0
- package/dist/tools/types.js +2 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/transport/http.d.ts +23 -0
- package/dist/transport/http.js +83 -0
- package/dist/transport/http.js.map +1 -0
- package/dist/youtube/api.d.ts +47 -0
- package/dist/youtube/api.js +176 -0
- package/dist/youtube/api.js.map +1 -0
- package/dist/youtube/transcripts.d.ts +65 -0
- package/dist/youtube/transcripts.js +321 -0
- package/dist/youtube/transcripts.js.map +1 -0
- package/dist/youtube/ytdlp.d.ts +35 -0
- package/dist/youtube/ytdlp.js +145 -0
- package/dist/youtube/ytdlp.js.map +1 -0
- package/package.json +65 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Research tools: anyone's channel, not just yours.
|
|
3
|
+
*
|
|
4
|
+
* The difference between these and a thin API wrapper is that YouTube's search
|
|
5
|
+
* endpoint returns no statistics at all. No views, no subscriber counts, no
|
|
6
|
+
* duration. A model handed those results cannot tell a hit from a flop, so it
|
|
7
|
+
* guesses confidently. Every search here joins the stats back on before it
|
|
8
|
+
* returns, which costs one extra call and removes the guessing.
|
|
9
|
+
*
|
|
10
|
+
* `analyze_channel` scores each video against that channel's OWN median rather
|
|
11
|
+
* than an absolute view count. "Did this beat what this channel normally does"
|
|
12
|
+
* is the only form of the question that transfers between a 2,000-subscriber
|
|
13
|
+
* channel and a 2,000,000-subscriber one.
|
|
14
|
+
*/
|
|
15
|
+
import { z } from "zod";
|
|
16
|
+
import { durationToSeconds } from "../youtube/api.js";
|
|
17
|
+
import { clamp, defineTool } from "./kit.js";
|
|
18
|
+
const num = (v) => (v === undefined ? null : Number(v));
|
|
19
|
+
const account = z
|
|
20
|
+
.string()
|
|
21
|
+
.optional()
|
|
22
|
+
.describe("Which connected channel's quota to spend. Only matters when several are connected.");
|
|
23
|
+
function median(values) {
|
|
24
|
+
if (values.length === 0)
|
|
25
|
+
return 0;
|
|
26
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
27
|
+
const mid = Math.floor(sorted.length / 2);
|
|
28
|
+
return sorted.length % 2 ? (sorted[mid] ?? 0) : ((sorted[mid - 1] ?? 0) + (sorted[mid] ?? 0)) / 2;
|
|
29
|
+
}
|
|
30
|
+
/** The Data API takes at most 50 ids per call. */
|
|
31
|
+
function chunk(items, size = 50) {
|
|
32
|
+
const out = [];
|
|
33
|
+
for (let i = 0; i < items.length; i += size)
|
|
34
|
+
out.push(items.slice(i, i + size));
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
/** Accepts `@handle`, a `UC…` id, or any channel URL, because people paste all three. */
|
|
38
|
+
function channelQuery(input) {
|
|
39
|
+
const raw = input.trim();
|
|
40
|
+
const byId = /^UC[\w-]{22}$/.test(raw) ? raw : raw.match(/\/channel\/(UC[\w-]{22})/)?.[1];
|
|
41
|
+
if (byId)
|
|
42
|
+
return { id: byId };
|
|
43
|
+
const handle = raw.match(/@([\w.-]+)/)?.[1] ?? raw.replace(/^@/, "");
|
|
44
|
+
return { forHandle: `@${handle}` };
|
|
45
|
+
}
|
|
46
|
+
export const researchTools = [
|
|
47
|
+
defineTool({
|
|
48
|
+
name: "search_videos",
|
|
49
|
+
title: "Search videos",
|
|
50
|
+
description: "Search YouTube and get results WITH view counts, likes and duration attached. Plain API search returns none of those, so use this whenever you need to judge whether a result actually performed rather than just matched. Search has its own allowance of 100 calls a day, separate from the 10,000-unit pool the other endpoints share, so use it deliberately rather than as a first guess.",
|
|
51
|
+
schema: {
|
|
52
|
+
query: z.string().min(1),
|
|
53
|
+
max_results: z.number().min(1).max(50).optional().describe("Default 25."),
|
|
54
|
+
order: z.enum(["relevance", "date", "viewCount", "rating", "title"]).optional(),
|
|
55
|
+
published_after: z.string().optional().describe("RFC 3339, e.g. 2026-01-01T00:00:00Z"),
|
|
56
|
+
published_before: z.string().optional(),
|
|
57
|
+
channel_id: z.string().optional().describe("Restrict to one channel."),
|
|
58
|
+
min_views: z.number().optional().describe("Drop results below this, applied after the stats join."),
|
|
59
|
+
account,
|
|
60
|
+
},
|
|
61
|
+
risk: "read",
|
|
62
|
+
handler: async (args, ctx) => {
|
|
63
|
+
const client = ctx.clientFor(args.account);
|
|
64
|
+
const search = await client.get("/search", {
|
|
65
|
+
part: "snippet",
|
|
66
|
+
type: "video",
|
|
67
|
+
q: args.query,
|
|
68
|
+
maxResults: clamp(args.max_results, 25),
|
|
69
|
+
order: args.order,
|
|
70
|
+
publishedAfter: args.published_after,
|
|
71
|
+
publishedBefore: args.published_before,
|
|
72
|
+
channelId: args.channel_id,
|
|
73
|
+
});
|
|
74
|
+
const ids = (search.items ?? []).map((i) => i.id?.videoId).filter((v) => !!v);
|
|
75
|
+
if (ids.length === 0)
|
|
76
|
+
return "No results.";
|
|
77
|
+
const details = await client.get("/videos", {
|
|
78
|
+
part: "snippet,statistics,contentDetails",
|
|
79
|
+
id: ids.join(","),
|
|
80
|
+
});
|
|
81
|
+
let rows = (details.items ?? []).map((v) => ({
|
|
82
|
+
id: v.id ?? "",
|
|
83
|
+
title: v.snippet?.title ?? "",
|
|
84
|
+
channel: v.snippet?.channelTitle ?? "",
|
|
85
|
+
published: v.snippet?.publishedAt?.slice(0, 10) ?? "",
|
|
86
|
+
views: num(v.statistics?.viewCount) ?? 0,
|
|
87
|
+
seconds: durationToSeconds(v.contentDetails?.duration),
|
|
88
|
+
}));
|
|
89
|
+
if (args.min_views !== undefined)
|
|
90
|
+
rows = rows.filter((r) => r.views >= args.min_views);
|
|
91
|
+
rows.sort((a, b) => b.views - a.views);
|
|
92
|
+
return `${rows.length} result(s)\n\n${rows
|
|
93
|
+
.map((r) => `${r.views.toLocaleString()} views · ${r.published} · ${r.seconds ? `${Math.round(r.seconds / 60)}m` : "?"} · ${r.channel}\n${r.title}\nhttps://youtu.be/${r.id}`)
|
|
94
|
+
.join("\n\n")}`;
|
|
95
|
+
},
|
|
96
|
+
}),
|
|
97
|
+
defineTool({
|
|
98
|
+
name: "get_channel",
|
|
99
|
+
title: "Look up a channel",
|
|
100
|
+
description: "Look up any channel by @handle, id or URL. Returns subscribers, total views, video count and the uploads playlist id. Subscriber counts are rounded by YouTube itself above 1,000 and read as `hidden` when the owner hides them.",
|
|
101
|
+
schema: { channel: z.string().describe("@handle, channel id (UC…), or a channel URL."), account },
|
|
102
|
+
risk: "read",
|
|
103
|
+
handler: async (args, ctx) => {
|
|
104
|
+
const res = await ctx.clientFor(args.account).get("/channels", {
|
|
105
|
+
part: "snippet,statistics,contentDetails",
|
|
106
|
+
...channelQuery(args.channel),
|
|
107
|
+
});
|
|
108
|
+
const c = res.items?.[0];
|
|
109
|
+
if (!c)
|
|
110
|
+
throw new Error(`No channel found for "${args.channel}".`);
|
|
111
|
+
return [
|
|
112
|
+
c.snippet?.title,
|
|
113
|
+
c.snippet?.customUrl ? `@${c.snippet.customUrl.replace(/^@/, "")}` : null,
|
|
114
|
+
`id: ${c.id}`,
|
|
115
|
+
`subscribers: ${num(c.statistics?.subscriberCount)?.toLocaleString() ?? "hidden"}`,
|
|
116
|
+
`videos: ${num(c.statistics?.videoCount)?.toLocaleString() ?? "?"}`,
|
|
117
|
+
`total views: ${num(c.statistics?.viewCount)?.toLocaleString() ?? "?"}`,
|
|
118
|
+
`created: ${c.snippet?.publishedAt?.slice(0, 10) ?? "?"}`,
|
|
119
|
+
`uploads playlist: ${c.contentDetails?.relatedPlaylists?.uploads ?? "?"}`,
|
|
120
|
+
]
|
|
121
|
+
.filter(Boolean)
|
|
122
|
+
.join("\n");
|
|
123
|
+
},
|
|
124
|
+
}),
|
|
125
|
+
defineTool({
|
|
126
|
+
name: "analyze_channel",
|
|
127
|
+
title: "Analyze a channel's performance",
|
|
128
|
+
description: "Score a channel's recent videos against its OWN median views, so you can see which ones genuinely outperformed rather than which are simply oldest. Returns a multiple per video, so 3.2x means it did three times that channel's normal numbers. Use this before modelling anyone's content. Shorts are flagged because their views are not comparable to long-form on the same channel.",
|
|
129
|
+
schema: {
|
|
130
|
+
channel: z.string().describe("@handle, channel id, or URL."),
|
|
131
|
+
limit: z.number().min(5).max(200).optional().describe("How many recent videos to score. Default 30."),
|
|
132
|
+
account,
|
|
133
|
+
},
|
|
134
|
+
risk: "read",
|
|
135
|
+
handler: async (args, ctx) => {
|
|
136
|
+
const client = ctx.clientFor(args.account);
|
|
137
|
+
const chan = await client.get("/channels", {
|
|
138
|
+
part: "snippet,statistics,contentDetails",
|
|
139
|
+
...channelQuery(args.channel),
|
|
140
|
+
});
|
|
141
|
+
const c = chan.items?.[0];
|
|
142
|
+
const uploads = c?.contentDetails?.relatedPlaylists?.uploads;
|
|
143
|
+
if (!c || !uploads)
|
|
144
|
+
throw new Error(`No channel found for "${args.channel}".`);
|
|
145
|
+
const want = args.limit ?? 30;
|
|
146
|
+
const ids = [];
|
|
147
|
+
let pageToken;
|
|
148
|
+
while (ids.length < want) {
|
|
149
|
+
const page = await client.get("/playlistItems", {
|
|
150
|
+
part: "contentDetails",
|
|
151
|
+
playlistId: uploads,
|
|
152
|
+
maxResults: Math.min(50, want - ids.length),
|
|
153
|
+
pageToken,
|
|
154
|
+
});
|
|
155
|
+
for (const it of page.items ?? []) {
|
|
156
|
+
const v = it.contentDetails?.videoId;
|
|
157
|
+
if (v)
|
|
158
|
+
ids.push(v);
|
|
159
|
+
}
|
|
160
|
+
pageToken = page.nextPageToken;
|
|
161
|
+
if (!pageToken)
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
const videos = [];
|
|
165
|
+
for (const batch of chunk(ids)) {
|
|
166
|
+
const res = await client.get("/videos", {
|
|
167
|
+
part: "snippet,statistics,contentDetails",
|
|
168
|
+
id: batch.join(","),
|
|
169
|
+
});
|
|
170
|
+
videos.push(...(res.items ?? []));
|
|
171
|
+
}
|
|
172
|
+
const rows = videos.map((v) => ({
|
|
173
|
+
id: v.id ?? "",
|
|
174
|
+
title: v.snippet?.title ?? "",
|
|
175
|
+
published: v.snippet?.publishedAt?.slice(0, 10) ?? "",
|
|
176
|
+
views: num(v.statistics?.viewCount) ?? 0,
|
|
177
|
+
seconds: durationToSeconds(v.contentDetails?.duration) ?? 0,
|
|
178
|
+
}));
|
|
179
|
+
const med = median(rows.map((r) => r.views));
|
|
180
|
+
const scored = rows
|
|
181
|
+
.map((r) => ({
|
|
182
|
+
...r,
|
|
183
|
+
multiple: med > 0 ? r.views / med : 0,
|
|
184
|
+
// Three minutes or under is a Short in practice, and Shorts views are
|
|
185
|
+
// not comparable to long-form on the same channel.
|
|
186
|
+
isShort: r.seconds > 0 && r.seconds <= 180,
|
|
187
|
+
}))
|
|
188
|
+
.sort((a, b) => b.multiple - a.multiple);
|
|
189
|
+
const fmt = (r) => `${r.multiple.toFixed(2)}x · ${r.views.toLocaleString()} views · ${r.published}${r.isShort ? " · short" : ""}\n${r.title}\nhttps://youtu.be/${r.id}`;
|
|
190
|
+
return [
|
|
191
|
+
`${c.snippet?.title} · ${num(c.statistics?.subscriberCount)?.toLocaleString() ?? "hidden"} subscribers`,
|
|
192
|
+
`scored ${scored.length} recent videos, median ${med.toLocaleString()} views`,
|
|
193
|
+
"",
|
|
194
|
+
"## Beat the median",
|
|
195
|
+
scored.filter((r) => r.multiple > 1).map(fmt).join("\n\n") || "none",
|
|
196
|
+
"",
|
|
197
|
+
"## Weakest five",
|
|
198
|
+
scored.filter((r) => r.multiple <= 1).slice(-5).map(fmt).join("\n\n") || "none",
|
|
199
|
+
].join("\n");
|
|
200
|
+
},
|
|
201
|
+
}),
|
|
202
|
+
defineTool({
|
|
203
|
+
name: "get_video",
|
|
204
|
+
title: "Get video detail",
|
|
205
|
+
description: "Full detail for one video: title, description, channel, views, likes, comments, duration and tags. Tags are only returned to the channel that owns the video, so they read as empty for anyone else's.",
|
|
206
|
+
schema: { video: z.string().describe("Video id or URL."), account },
|
|
207
|
+
risk: "read",
|
|
208
|
+
handler: async (args, ctx) => {
|
|
209
|
+
const id = args.video.match(/[\w-]{11}/)?.[0] ?? args.video;
|
|
210
|
+
const res = await ctx.clientFor(args.account).get("/videos", {
|
|
211
|
+
part: "snippet,statistics,contentDetails",
|
|
212
|
+
id,
|
|
213
|
+
});
|
|
214
|
+
const v = res.items?.[0];
|
|
215
|
+
if (!v)
|
|
216
|
+
throw new Error(`No video found for "${args.video}".`);
|
|
217
|
+
const secs = durationToSeconds(v.contentDetails?.duration);
|
|
218
|
+
return [
|
|
219
|
+
v.snippet?.title,
|
|
220
|
+
`${v.snippet?.channelTitle} · ${v.snippet?.publishedAt?.slice(0, 10)}`,
|
|
221
|
+
`${num(v.statistics?.viewCount)?.toLocaleString() ?? "?"} views · ${num(v.statistics?.likeCount)?.toLocaleString() ?? "?"} likes · ${num(v.statistics?.commentCount)?.toLocaleString() ?? "?"} comments`,
|
|
222
|
+
secs ? `duration: ${Math.floor(secs / 60)}m ${secs % 60}s` : null,
|
|
223
|
+
v.snippet?.tags?.length ? `tags: ${v.snippet.tags.join(", ")}` : null,
|
|
224
|
+
"",
|
|
225
|
+
v.snippet?.description ?? "",
|
|
226
|
+
]
|
|
227
|
+
.filter((l) => l !== null)
|
|
228
|
+
.join("\n");
|
|
229
|
+
},
|
|
230
|
+
}),
|
|
231
|
+
].map((t) => t);
|
|
232
|
+
//# sourceMappingURL=research.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"research.js","sourceRoot":"","sources":["../../src/tools/research.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transcript tools.
|
|
3
|
+
*
|
|
4
|
+
* These are the only tools that work with no credentials of any kind, and they
|
|
5
|
+
* read anyone's video rather than only your own. For most people this is the
|
|
6
|
+
* reason to install the server at all, so it is the group listed first.
|
|
7
|
+
*/
|
|
8
|
+
import { type AnyToolSpec } from "./kit.js";
|
|
9
|
+
export declare const transcriptTools: AnyToolSpec[];
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transcript tools.
|
|
3
|
+
*
|
|
4
|
+
* These are the only tools that work with no credentials of any kind, and they
|
|
5
|
+
* read anyone's video rather than only your own. For most people this is the
|
|
6
|
+
* reason to install the server at all, so it is the group listed first.
|
|
7
|
+
*/
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import { fetchTranscript, listTracks, parseVideoId, searchSegments, toPlainText, toTimestampedText, } from "../youtube/transcripts.js";
|
|
10
|
+
import { defineTool } from "./kit.js";
|
|
11
|
+
const video = z
|
|
12
|
+
.string()
|
|
13
|
+
.describe("Video id or any YouTube URL: watch, youtu.be, Shorts, embed or live.");
|
|
14
|
+
export const transcriptTools = [
|
|
15
|
+
defineTool({
|
|
16
|
+
name: "get_transcript",
|
|
17
|
+
title: "Get transcript",
|
|
18
|
+
description: "Read the transcript of ANY public YouTube video, not just your own. Needs no API key, no connected account and no quota. Returns prose by default, or timestamped lines when you need to cite a moment. Videos with captions disabled have no transcript and nothing can recover one.",
|
|
19
|
+
schema: {
|
|
20
|
+
video,
|
|
21
|
+
language: z
|
|
22
|
+
.string()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("Language code such as `en`, `es`, `de`. Defaults to English, then whatever exists."),
|
|
25
|
+
timestamps: z
|
|
26
|
+
.boolean()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe("Return `[m:ss] text` lines instead of prose. Use when you need to point at a moment."),
|
|
29
|
+
group_seconds: z
|
|
30
|
+
.number()
|
|
31
|
+
.min(5)
|
|
32
|
+
.max(300)
|
|
33
|
+
.optional()
|
|
34
|
+
.describe("Seconds of speech per timestamped line. Default 30."),
|
|
35
|
+
},
|
|
36
|
+
risk: "read",
|
|
37
|
+
handler: async ({ video: v, language, timestamps, group_seconds }, ctx) => {
|
|
38
|
+
const t = await fetchTranscript(v, { language: language ?? ctx.config.defaultLanguage });
|
|
39
|
+
const body = timestamps
|
|
40
|
+
? toTimestampedText(t.segments, group_seconds ?? 30)
|
|
41
|
+
: toPlainText(t.segments);
|
|
42
|
+
return (`video: ${t.videoId}\nlanguage: ${t.language}${t.autoGenerated ? " (auto-generated)" : ""}\n` +
|
|
43
|
+
`segments: ${t.segments.length}\n\n${body}`);
|
|
44
|
+
},
|
|
45
|
+
}),
|
|
46
|
+
defineTool({
|
|
47
|
+
name: "list_transcript_languages",
|
|
48
|
+
title: "List transcript languages",
|
|
49
|
+
description: "List every caption language on a video and whether each was written by a human or auto-generated. Human captions are more accurate. Call this before get_transcript when the video may not be in English.",
|
|
50
|
+
schema: { video },
|
|
51
|
+
risk: "read",
|
|
52
|
+
handler: async ({ video: v }) => {
|
|
53
|
+
const id = parseVideoId(v);
|
|
54
|
+
if (!id)
|
|
55
|
+
throw new Error(`Could not read a video id out of "${v}".`);
|
|
56
|
+
const tracks = await listTracks(id);
|
|
57
|
+
if (tracks.length === 0)
|
|
58
|
+
return "This video has no captions.";
|
|
59
|
+
return tracks
|
|
60
|
+
.map((t) => `${t.languageCode}\t${t.languageName}${t.autoGenerated ? "\t(auto-generated)" : ""}`)
|
|
61
|
+
.join("\n");
|
|
62
|
+
},
|
|
63
|
+
}),
|
|
64
|
+
defineTool({
|
|
65
|
+
name: "search_transcript",
|
|
66
|
+
title: "Search inside a video",
|
|
67
|
+
description: "Find every place a phrase is said in a video and get the timestamps, each as a link that jumps to that second. Use this instead of pulling a whole transcript when you only need to locate something.",
|
|
68
|
+
schema: {
|
|
69
|
+
video,
|
|
70
|
+
query: z.string().min(1).describe("The phrase to find. Case-insensitive substring match."),
|
|
71
|
+
language: z.string().optional(),
|
|
72
|
+
context_segments: z
|
|
73
|
+
.number()
|
|
74
|
+
.min(0)
|
|
75
|
+
.max(5)
|
|
76
|
+
.optional()
|
|
77
|
+
.describe("Caption segments either side to include for context. Default 1."),
|
|
78
|
+
},
|
|
79
|
+
risk: "read",
|
|
80
|
+
handler: async ({ video: v, query, language, context_segments }, ctx) => {
|
|
81
|
+
const t = await fetchTranscript(v, { language: language ?? ctx.config.defaultLanguage });
|
|
82
|
+
const hits = searchSegments(t.segments, query, context_segments ?? 1);
|
|
83
|
+
if (hits.length === 0)
|
|
84
|
+
return `"${query}" is not said in this video.`;
|
|
85
|
+
const body = hits
|
|
86
|
+
.map((h) => `[${h.timestamp}] https://youtu.be/${t.videoId}?t=${Math.floor(h.start)}\n${h.text}`)
|
|
87
|
+
.join("\n\n");
|
|
88
|
+
return `${hits.length} match(es)\n\n${body}`;
|
|
89
|
+
},
|
|
90
|
+
}),
|
|
91
|
+
defineTool({
|
|
92
|
+
name: "get_transcripts",
|
|
93
|
+
title: "Get several transcripts",
|
|
94
|
+
description: "Fetch transcripts for up to 20 videos in one call. A video with no captions is reported in place rather than failing the batch. Use it to compare how a set of videos open, or to build a corpus before analyzing it.",
|
|
95
|
+
schema: {
|
|
96
|
+
videos: z.array(video).min(1).max(20).describe("Up to 20 video ids or URLs."),
|
|
97
|
+
language: z.string().optional(),
|
|
98
|
+
max_chars_each: z
|
|
99
|
+
.number()
|
|
100
|
+
.min(200)
|
|
101
|
+
.optional()
|
|
102
|
+
.describe("Truncate each transcript to this many characters. Useful when you only need the openings."),
|
|
103
|
+
},
|
|
104
|
+
risk: "read",
|
|
105
|
+
handler: async ({ videos, language, max_chars_each }, ctx) => {
|
|
106
|
+
const parts = [];
|
|
107
|
+
for (const v of videos) {
|
|
108
|
+
try {
|
|
109
|
+
const t = await fetchTranscript(v, { language: language ?? ctx.config.defaultLanguage });
|
|
110
|
+
let text = toPlainText(t.segments);
|
|
111
|
+
if (max_chars_each && text.length > max_chars_each)
|
|
112
|
+
text = `${text.slice(0, max_chars_each)}…`;
|
|
113
|
+
parts.push(`### ${t.videoId} (${t.language})\n${text}`);
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
parts.push(`### ${v}\nNo transcript: ${err instanceof Error ? err.message : String(err)}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return parts.join("\n\n");
|
|
120
|
+
},
|
|
121
|
+
}),
|
|
122
|
+
].map((t) => t);
|
|
123
|
+
//# sourceMappingURL=transcripts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcripts.js","sourceRoot":"","sources":["../../src/tools/transcripts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/tools/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP transport, for running the server somewhere always on.
|
|
3
|
+
*
|
|
4
|
+
* Streamable HTTP per the 2025-03-26 spec, stateless: every request builds its
|
|
5
|
+
* own transport and tears it down. No session map means no session leak, which
|
|
6
|
+
* matters more here than the reconnect support a stateful server would buy.
|
|
7
|
+
*
|
|
8
|
+
* Bound to 127.0.0.1 by default. A connected refresh token can upload to and
|
|
9
|
+
* delete from a real channel, so a server that binds 0.0.0.0 without being
|
|
10
|
+
* asked is a mistake that only has to be made once. YOUTUBE_HTTP_HOST is there
|
|
11
|
+
* for people who mean it, and YOUTUBE_HTTP_TOKEN should be set with it.
|
|
12
|
+
*/
|
|
13
|
+
import type { BuiltServer } from "../server.js";
|
|
14
|
+
export type HttpOptions = {
|
|
15
|
+
port: number;
|
|
16
|
+
host: string;
|
|
17
|
+
/** When set, every request must send `Authorization: Bearer <token>`. */
|
|
18
|
+
token?: string;
|
|
19
|
+
};
|
|
20
|
+
export declare function httpOptionsFromEnv(argv?: string[]): HttpOptions;
|
|
21
|
+
export declare function startHttpServer(built: BuiltServer, options: HttpOptions): Promise<{
|
|
22
|
+
close: () => Promise<void>;
|
|
23
|
+
}>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP transport, for running the server somewhere always on.
|
|
3
|
+
*
|
|
4
|
+
* Streamable HTTP per the 2025-03-26 spec, stateless: every request builds its
|
|
5
|
+
* own transport and tears it down. No session map means no session leak, which
|
|
6
|
+
* matters more here than the reconnect support a stateful server would buy.
|
|
7
|
+
*
|
|
8
|
+
* Bound to 127.0.0.1 by default. A connected refresh token can upload to and
|
|
9
|
+
* delete from a real channel, so a server that binds 0.0.0.0 without being
|
|
10
|
+
* asked is a mistake that only has to be made once. YOUTUBE_HTTP_HOST is there
|
|
11
|
+
* for people who mean it, and YOUTUBE_HTTP_TOKEN should be set with it.
|
|
12
|
+
*/
|
|
13
|
+
import { createServer } from "node:http";
|
|
14
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
15
|
+
export function httpOptionsFromEnv(argv = []) {
|
|
16
|
+
const flag = argv.find((a) => a.startsWith("--port="));
|
|
17
|
+
const port = Number(flag?.split("=")[1] ?? process.env.YOUTUBE_HTTP_PORT ?? 8787);
|
|
18
|
+
return {
|
|
19
|
+
port: Number.isFinite(port) && port > 0 ? port : 8787,
|
|
20
|
+
host: process.env.YOUTUBE_HTTP_HOST || "127.0.0.1",
|
|
21
|
+
token: process.env.YOUTUBE_HTTP_TOKEN || undefined,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
async function readBody(req) {
|
|
25
|
+
const chunks = [];
|
|
26
|
+
for await (const chunk of req)
|
|
27
|
+
chunks.push(chunk);
|
|
28
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
29
|
+
if (!raw)
|
|
30
|
+
return undefined;
|
|
31
|
+
try {
|
|
32
|
+
return JSON.parse(raw);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export async function startHttpServer(built, options) {
|
|
39
|
+
const http = createServer((req, res) => {
|
|
40
|
+
void handle(built, options, req, res).catch((error) => {
|
|
41
|
+
if (!res.headersSent)
|
|
42
|
+
res.writeHead(500, { "content-type": "application/json" });
|
|
43
|
+
res.end(JSON.stringify({
|
|
44
|
+
jsonrpc: "2.0",
|
|
45
|
+
error: { code: -32603, message: error?.message ?? "internal error" },
|
|
46
|
+
id: null,
|
|
47
|
+
}));
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
await new Promise((resolve) => http.listen(options.port, options.host, resolve));
|
|
51
|
+
process.stderr.write(`[youtube-mcp] listening on http://${options.host}:${options.port}/mcp${options.token ? " (bearer token required)" : ""}\n`);
|
|
52
|
+
return {
|
|
53
|
+
close: () => new Promise((resolve) => {
|
|
54
|
+
http.close(() => resolve());
|
|
55
|
+
}),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
async function handle(built, options, req, res) {
|
|
59
|
+
if (req.url === "/health") {
|
|
60
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
61
|
+
res.end(JSON.stringify({ ok: true, tools: built.toolCount, accounts: built.config.accounts.length }));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (options.token) {
|
|
65
|
+
if ((req.headers.authorization ?? "") !== `Bearer ${options.token}`) {
|
|
66
|
+
res.writeHead(401, { "content-type": "application/json" });
|
|
67
|
+
res.end(JSON.stringify({ error: "unauthorized" }));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (req.method === "DELETE") {
|
|
72
|
+
// Stateless: there is no session to end, and saying so beats a 404.
|
|
73
|
+
res.writeHead(204).end();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
// A fresh transport per request. `sessionIdGenerator: undefined` is what puts
|
|
77
|
+
// the SDK into stateless mode.
|
|
78
|
+
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
|
79
|
+
res.on("close", () => void transport.close());
|
|
80
|
+
await built.server.connect(transport);
|
|
81
|
+
await transport.handleRequest(req, res, await readBody(req));
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/transport/http.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* YouTube Data + Analytics API client.
|
|
3
|
+
*
|
|
4
|
+
* Two credential paths, and which one you have decides which tools work:
|
|
5
|
+
*
|
|
6
|
+
* API key: anyone's public data. Search, channels, public video stats.
|
|
7
|
+
* No consent screen, no user. Cheapest to set up.
|
|
8
|
+
* OAuth: your own channels. Uploads, playlists, comments as the owner,
|
|
9
|
+
* and Analytics, which has no API-key path at all.
|
|
10
|
+
*
|
|
11
|
+
* Multi-account is the default rather than a feature. A creator with several
|
|
12
|
+
* channels has to be able to say which one an action runs against, so every
|
|
13
|
+
* account-scoped tool takes `account` and the server refuses to guess when
|
|
14
|
+
* more than one is connected.
|
|
15
|
+
*/
|
|
16
|
+
import type { Account } from "../config.js";
|
|
17
|
+
export declare class YouTubeApiError extends Error {
|
|
18
|
+
readonly status: number;
|
|
19
|
+
readonly reason?: string | undefined;
|
|
20
|
+
constructor(message: string, status: number, reason?: string | undefined);
|
|
21
|
+
/** The shape the CLI prints on stderr, so a script parses one thing. */
|
|
22
|
+
toJSON(): {
|
|
23
|
+
error: string;
|
|
24
|
+
status: number;
|
|
25
|
+
reason?: string;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export type Credentials = {
|
|
29
|
+
apiKey?: string;
|
|
30
|
+
account?: Account;
|
|
31
|
+
};
|
|
32
|
+
export declare class YouTubeClient {
|
|
33
|
+
private readonly creds;
|
|
34
|
+
/** Refreshed tokens live here for the process lifetime, keyed by account id. */
|
|
35
|
+
private tokenCache;
|
|
36
|
+
constructor(creds: Credentials);
|
|
37
|
+
/** An access token for the account, refreshing when it is within a minute of expiry. */
|
|
38
|
+
private accessToken;
|
|
39
|
+
private request;
|
|
40
|
+
get<T>(path: string, params?: Record<string, unknown>, requireAuth?: boolean): Promise<T>;
|
|
41
|
+
post<T>(path: string, params: Record<string, unknown>, body: unknown): Promise<T>;
|
|
42
|
+
put<T>(path: string, params: Record<string, unknown>, body: unknown): Promise<T>;
|
|
43
|
+
delete<T>(path: string, params: Record<string, unknown>): Promise<T>;
|
|
44
|
+
analytics<T>(params: Record<string, unknown>): Promise<T>;
|
|
45
|
+
}
|
|
46
|
+
/** ISO 8601 durations (`PT4M13S`) are unreadable in output. Seconds are not. */
|
|
47
|
+
export declare function durationToSeconds(iso?: string): number | null;
|