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 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:
@@ -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 — the same one-shot tolerance given above to a
266
- // thrown network error; a 503 here previously failed the whole tool call
267
- // immediately instead of getting that same retry.
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 = permanent error (invalid run ID, bad token) — fail fast instead of polling to timeout
293
- if (statusResp.status >= 400 && statusResp.status < 500) {
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
- console.warn(`[analyze_channel] Gemini returned unparseable JSON for video ${videoId}, skipping`);
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 — surface it as a total
509
- // failure so analyzeChannel's note reports a Gemini outage instead of
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
- if (process.argv.includes("--help") || process.argv.includes("-h")) {
17
- console.log(`mcp-youtube-intelligence v${packageVersion} — MCP server for YouTube channel intelligence
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.4",
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",