mcp-youtube-intelligence 0.2.4 → 0.2.6
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/README.md +10 -0
- package/build/analyze_channel.js +22 -12
- package/build/cli.js +30 -0
- package/build/index.js +4 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,6 +33,16 @@ You need API keys for three services:
|
|
|
33
33
|
npm install -g mcp-youtube-intelligence
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
+
## CLI flags
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
mcp-youtube-intelligence --version # or -v — print the installed version and exit
|
|
40
|
+
mcp-youtube-intelligence --help # or -h — print usage and exit
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Running the command with no flags starts the MCP server itself (stdio transport) — this is what
|
|
44
|
+
an MCP client config invokes; it's not meant to be run bare in a terminal for interactive use.
|
|
45
|
+
|
|
36
46
|
## Usage
|
|
37
47
|
|
|
38
48
|
Add to your Claude Desktop / MCP client config:
|
package/build/analyze_channel.js
CHANGED
|
@@ -97,6 +97,9 @@ export function getGeminiApiKey() {
|
|
|
97
97
|
// ---------------------------------------------------------------------------
|
|
98
98
|
export function parseChannelInput(input) {
|
|
99
99
|
const trimmed = input.trim();
|
|
100
|
+
if (!trimmed) {
|
|
101
|
+
throw new Error("channel_url must not be empty.");
|
|
102
|
+
}
|
|
100
103
|
// Bare @handle — e.g. @fireship or @web.dev (periods allowed in handles)
|
|
101
104
|
if (/^@[\w.-]+$/.test(trimmed))
|
|
102
105
|
return { type: "handle", value: trimmed };
|
|
@@ -261,10 +264,10 @@ export async function runApifyTranscriptScraper(videoUrls, token) {
|
|
|
261
264
|
if (runResp === undefined) {
|
|
262
265
|
runResp = await startRun();
|
|
263
266
|
}
|
|
264
|
-
else if (!runResp.ok && runResp.status >= 500) {
|
|
265
|
-
// Retry once on a transient 5xx
|
|
266
|
-
//
|
|
267
|
-
//
|
|
267
|
+
else if (!runResp.ok && (runResp.status >= 500 || runResp.status === 429)) {
|
|
268
|
+
// Retry once on a transient 5xx, or a 429 — Apify returns 429 when the account's
|
|
269
|
+
// concurrent-actor-run limit is hit, which clears within seconds, unlike a bad
|
|
270
|
+
// token or malformed request. Same tolerance already given to Gemini below.
|
|
268
271
|
runResp = await startRun();
|
|
269
272
|
}
|
|
270
273
|
if (!runResp.ok) {
|
|
@@ -289,11 +292,13 @@ export async function runApifyTranscriptScraper(videoUrls, token) {
|
|
|
289
292
|
continue;
|
|
290
293
|
}
|
|
291
294
|
if (!statusResp.ok) {
|
|
292
|
-
// 4xx
|
|
293
|
-
|
|
295
|
+
// Most 4xx are permanent (invalid run ID, bad token) — fail fast instead of
|
|
296
|
+
// polling to timeout. 429 is the exception: Apify's own rate limit, which the
|
|
297
|
+
// next scheduled poll (APIFY_POLL_INTERVAL_MS later) already backs off past.
|
|
298
|
+
if (statusResp.status >= 400 && statusResp.status < 500 && statusResp.status !== 429) {
|
|
294
299
|
throw new Error(`Apify poll error ${statusResp.status} for run ${runId}: ${await statusResp.text()}`);
|
|
295
300
|
}
|
|
296
|
-
continue; // 5xx or network transient — keep polling
|
|
301
|
+
continue; // 5xx, 429, or network transient — keep polling
|
|
297
302
|
}
|
|
298
303
|
let statusBody;
|
|
299
304
|
try {
|
|
@@ -329,7 +334,7 @@ export async function runApifyTranscriptScraper(videoUrls, token) {
|
|
|
329
334
|
if (itemsResp === undefined) {
|
|
330
335
|
itemsResp = await fetchItems();
|
|
331
336
|
}
|
|
332
|
-
else if (!itemsResp.ok && itemsResp.status >= 500) {
|
|
337
|
+
else if (!itemsResp.ok && (itemsResp.status >= 500 || itemsResp.status === 429)) {
|
|
333
338
|
itemsResp = await fetchItems();
|
|
334
339
|
}
|
|
335
340
|
if (!itemsResp.ok)
|
|
@@ -488,7 +493,12 @@ ${truncated}`;
|
|
|
488
493
|
parsed = JSON.parse(text);
|
|
489
494
|
}
|
|
490
495
|
catch {
|
|
491
|
-
|
|
496
|
+
// Counts toward the same "every attempted video produced nothing usable"
|
|
497
|
+
// check below as an HTTP-level failure — an all-malformed-JSON response is
|
|
498
|
+
// just as much a Gemini outage as an all-5xx one, and previously only the
|
|
499
|
+
// latter was surfaced as a thrown error instead of a silent empty success.
|
|
500
|
+
lastRequestError = `Gemini returned unparseable JSON for video ${videoId}`;
|
|
501
|
+
console.warn(`[analyze_channel] ${lastRequestError}, skipping`);
|
|
492
502
|
continue;
|
|
493
503
|
}
|
|
494
504
|
results.push({
|
|
@@ -505,9 +515,9 @@ ${truncated}`;
|
|
|
505
515
|
console.warn(`[analyze_channel] Gemini request failed for video ${videoId}, skipping: ${lastRequestError}`);
|
|
506
516
|
}
|
|
507
517
|
}
|
|
508
|
-
// Every attempted video failed at the HTTP level
|
|
509
|
-
// failure so analyzeChannel's note reports a
|
|
510
|
-
// silently claiming success with zero structured topics.
|
|
518
|
+
// Every attempted video failed, whether at the HTTP level or via unparseable
|
|
519
|
+
// JSON — surface it as a total failure so analyzeChannel's note reports a
|
|
520
|
+
// Gemini outage instead of silently claiming success with zero structured topics.
|
|
511
521
|
if (attempted > 0 && results.length === 0 && lastRequestError) {
|
|
512
522
|
throw new Error(lastRequestError);
|
|
513
523
|
}
|
package/build/cli.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure argv → output-text mapping for the --version/--help flags, split out of
|
|
3
|
+
* index.ts so it can be unit tested without importing index.ts itself — that
|
|
4
|
+
* module connects a stdio MCP transport as a top-level side effect on import,
|
|
5
|
+
* which would hang a test runner.
|
|
6
|
+
*/
|
|
7
|
+
const HELP_TEXT = (version) => `mcp-youtube-intelligence v${version} — MCP server for YouTube channel intelligence
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
mcp-youtube-intelligence [options]
|
|
11
|
+
|
|
12
|
+
Options:
|
|
13
|
+
--help, -h Show this help message
|
|
14
|
+
--version, -v Print the installed version
|
|
15
|
+
|
|
16
|
+
Tools provided:
|
|
17
|
+
analyze_channel Extract transcripts, topics, and competitive signals from a YouTube channel`;
|
|
18
|
+
/**
|
|
19
|
+
* Returns the text to print for --version/-v or --help/-h, or null if argv
|
|
20
|
+
* contains neither (the normal MCP-server-startup path). --version/-v takes
|
|
21
|
+
* precedence when both are present.
|
|
22
|
+
*/
|
|
23
|
+
export function getCliOutput(argv, version) {
|
|
24
|
+
if (argv.includes("--version") || argv.includes("-v"))
|
|
25
|
+
return version;
|
|
26
|
+
if (argv.includes("--help") || argv.includes("-h"))
|
|
27
|
+
return HELP_TEXT(version);
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=cli.js.map
|
package/build/index.js
CHANGED
|
@@ -11,19 +11,12 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
11
11
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
12
12
|
import { z } from "zod";
|
|
13
13
|
import { analyzeChannel } from "./analyze_channel.js";
|
|
14
|
+
import { getCliOutput } from "./cli.js";
|
|
14
15
|
const require = createRequire(import.meta.url);
|
|
15
16
|
const { version: packageVersion } = require("../package.json");
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
Usage:
|
|
20
|
-
mcp-youtube-intelligence [options]
|
|
21
|
-
|
|
22
|
-
Options:
|
|
23
|
-
--help, -h Show this help message
|
|
24
|
-
|
|
25
|
-
Tools provided:
|
|
26
|
-
analyze_channel Extract transcripts, topics, and competitive signals from a YouTube channel`);
|
|
17
|
+
const cliOutput = getCliOutput(process.argv, packageVersion);
|
|
18
|
+
if (cliOutput !== null) {
|
|
19
|
+
console.log(cliOutput);
|
|
27
20
|
process.exit(0);
|
|
28
21
|
}
|
|
29
22
|
const server = new McpServer({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-youtube-intelligence",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "MCP server for extracting structured intelligence from YouTube channels and videos — transcripts, topics, competitive signals",
|
|
5
5
|
"mcpName": "io.github.dmitriusan/mcp-youtube-intelligence",
|
|
6
6
|
"main": "build/index.js",
|