yt-transcript-strapi-plugin 0.1.5 → 0.2.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.
@@ -36,26 +36,6 @@ const FindTranscriptsSchema = z.object({
36
36
  pageSize: z.number().int().min(1).max(100).optional().default(25),
37
37
  sort: z.string().optional().default("createdAt:desc")
38
38
  });
39
- const ToolSchemas = {
40
- fetch_transcript: FetchTranscriptSchema,
41
- list_transcripts: ListTranscriptsSchema,
42
- get_transcript: GetTranscriptSchema,
43
- search_transcript: SearchTranscriptSchema,
44
- find_transcripts: FindTranscriptsSchema
45
- };
46
- function validateToolInput(toolName, input) {
47
- const schema2 = ToolSchemas[toolName];
48
- const result = schema2.safeParse(input);
49
- if (!result.success) {
50
- const errorMessages = result.error.issues.map((err) => {
51
- const path = err.path.length > 0 ? `${err.path.join(".")}: ` : "";
52
- return `${path}${err.message}`;
53
- });
54
- throw new Error(`Validation failed for ${toolName}:
55
- ${errorMessages.join("\n")}`);
56
- }
57
- return result.data;
58
- }
59
39
  function extractYouTubeID(urlOrID) {
60
40
  const regExpID = /^[a-zA-Z0-9_-]{11}$/;
61
41
  if (regExpID.test(urlOrID)) {
@@ -73,20 +53,6 @@ function extractYouTubeID(urlOrID) {
73
53
  }
74
54
  return null;
75
55
  }
76
- const fetchTranscriptTool = {
77
- name: "fetch_transcript",
78
- description: "Fetch a transcript from YouTube for a given video ID or URL. The transcript is saved to the database. Returns metadata and preview only to avoid context overflow. Use get_transcript to retrieve content.",
79
- inputSchema: {
80
- type: "object",
81
- properties: {
82
- videoId: {
83
- type: "string",
84
- description: 'YouTube video ID (e.g., "dQw4w9WgXcQ") or full YouTube URL'
85
- }
86
- },
87
- required: ["videoId"]
88
- }
89
- };
90
56
  function getVideoDurationMs$1(timecodes) {
91
57
  if (!timecodes || timecodes.length === 0) return 0;
92
58
  const lastEntry = timecodes[timecodes.length - 1];
@@ -120,11 +86,11 @@ function buildMetadataResponse(transcript2, previewLength, cached) {
120
86
  durationSeconds: Math.floor(durationMs / 1e3)
121
87
  },
122
88
  preview,
123
- usage: "Use get_transcript with videoId to retrieve full content, specific time ranges, or paginated chunks."
89
+ usage: "Use getTranscript with videoId to retrieve full content, specific time ranges, or paginated chunks."
124
90
  };
125
91
  }
126
- async function handleFetchTranscript(strapi, args) {
127
- const validatedArgs = validateToolInput("fetch_transcript", args);
92
+ async function execute$4(args, strapi) {
93
+ const validatedArgs = FetchTranscriptSchema.parse(args);
128
94
  const { videoId: videoIdOrUrl } = validatedArgs;
129
95
  const pluginConfig = await strapi.config.get("plugin::yt-transcript-strapi-plugin");
130
96
  const previewLength = pluginConfig?.previewLength || 500;
@@ -135,18 +101,7 @@ async function handleFetchTranscript(strapi, args) {
135
101
  const service2 = strapi.plugin("yt-transcript-strapi-plugin").service("service");
136
102
  const existingTranscript = await service2.findTranscript(videoId);
137
103
  if (existingTranscript) {
138
- return {
139
- content: [
140
- {
141
- type: "text",
142
- text: JSON.stringify(
143
- buildMetadataResponse(existingTranscript, previewLength, true),
144
- null,
145
- 2
146
- )
147
- }
148
- ]
149
- };
104
+ return buildMetadataResponse(existingTranscript, previewLength, true);
150
105
  }
151
106
  const transcriptData = await service2.getTranscript(videoId);
152
107
  if (!transcriptData || !transcriptData.fullTranscript) {
@@ -159,46 +114,17 @@ async function handleFetchTranscript(strapi, args) {
159
114
  transcriptWithTimeCodes: transcriptData.transcriptWithTimeCodes
160
115
  };
161
116
  const savedTranscript = await service2.saveTranscript(payload);
162
- return {
163
- content: [
164
- {
165
- type: "text",
166
- text: JSON.stringify(
167
- buildMetadataResponse(savedTranscript, previewLength, false),
168
- null,
169
- 2
170
- )
171
- }
172
- ]
173
- };
117
+ return buildMetadataResponse(savedTranscript, previewLength, false);
174
118
  }
175
- const listTranscriptsTool = {
176
- name: "list_transcripts",
177
- description: "List all saved YouTube transcripts from the database. Supports pagination and sorting.",
178
- inputSchema: {
179
- type: "object",
180
- properties: {
181
- page: {
182
- type: "number",
183
- description: "Page number (starts at 1)",
184
- default: 1
185
- },
186
- pageSize: {
187
- type: "number",
188
- description: "Number of items per page (max 100)",
189
- default: 25
190
- },
191
- sort: {
192
- type: "string",
193
- description: 'Sort order (e.g., "createdAt:desc", "title:asc")',
194
- default: "createdAt:desc"
195
- }
196
- },
197
- required: []
198
- }
119
+ const fetchTranscriptTool = {
120
+ name: "fetchTranscript",
121
+ description: "Fetch a transcript from YouTube for a given video ID or URL. The transcript is saved to the database. Returns metadata and preview only to avoid context overflow. Use getTranscript to retrieve content.",
122
+ schema: FetchTranscriptSchema,
123
+ execute: execute$4,
124
+ publicSafe: true
199
125
  };
200
- async function handleListTranscripts(strapi, args) {
201
- const validatedArgs = validateToolInput("list_transcripts", args);
126
+ async function execute$3(args, strapi) {
127
+ const validatedArgs = ListTranscriptsSchema.parse(args);
202
128
  const { page, pageSize, sort } = validatedArgs;
203
129
  const start = (page - 1) * pageSize;
204
130
  const transcripts = await strapi.documents("plugin::yt-transcript-strapi-plugin.transcript").findMany({
@@ -210,65 +136,21 @@ async function handleListTranscripts(strapi, args) {
210
136
  const allTranscripts = await strapi.documents("plugin::yt-transcript-strapi-plugin.transcript").findMany({});
211
137
  const total = allTranscripts.length;
212
138
  return {
213
- content: [
214
- {
215
- type: "text",
216
- text: JSON.stringify(
217
- {
218
- data: transcripts,
219
- pagination: {
220
- page,
221
- pageSize,
222
- total,
223
- pageCount: Math.ceil(total / pageSize)
224
- }
225
- },
226
- null,
227
- 2
228
- )
229
- }
230
- ]
139
+ data: transcripts,
140
+ pagination: {
141
+ page,
142
+ pageSize,
143
+ total,
144
+ pageCount: Math.ceil(total / pageSize)
145
+ }
231
146
  };
232
147
  }
233
- const getTranscriptTool = {
234
- name: "get_transcript",
235
- description: "Get a saved transcript by YouTube video ID. Returns metadata and preview by default. Use parameters to get full content or specific time ranges to avoid context overflow.",
236
- inputSchema: {
237
- type: "object",
238
- properties: {
239
- videoId: {
240
- type: "string",
241
- description: 'YouTube video ID (e.g., "dQw4w9WgXcQ") or full YouTube URL'
242
- },
243
- includeFullTranscript: {
244
- type: "boolean",
245
- description: "Include the complete transcript text. Warning: may cause context overflow for long videos. Default: false",
246
- default: false
247
- },
248
- includeTimecodes: {
249
- type: "boolean",
250
- description: "Include the transcript with timecodes array. Warning: significantly increases response size. Default: false",
251
- default: false
252
- },
253
- startTime: {
254
- type: "number",
255
- description: "Start time in seconds for fetching a specific portion of the transcript"
256
- },
257
- endTime: {
258
- type: "number",
259
- description: "End time in seconds for fetching a specific portion of the transcript"
260
- },
261
- chunkIndex: {
262
- type: "number",
263
- description: "Chunk index (0-based) when paginating through transcript. Use with chunkSize to paginate through long videos."
264
- },
265
- chunkSize: {
266
- type: "number",
267
- description: "Chunk size in seconds. Overrides config default. Use with chunkIndex for pagination."
268
- }
269
- },
270
- required: ["videoId"]
271
- }
148
+ const listTranscriptsTool = {
149
+ name: "listTranscripts",
150
+ description: "List all saved YouTube transcripts from the database. Supports pagination and sorting.",
151
+ schema: ListTranscriptsSchema,
152
+ execute: execute$3,
153
+ publicSafe: true
272
154
  };
273
155
  function getTranscriptForTimeRange(timecodes, startTimeMs, endTimeMs) {
274
156
  const entries = timecodes.filter(
@@ -292,8 +174,8 @@ function formatTime$1(ms) {
292
174
  }
293
175
  return `${minutes}:${seconds.toString().padStart(2, "0")}`;
294
176
  }
295
- async function handleGetTranscript(strapi, args) {
296
- const validatedArgs = validateToolInput("get_transcript", args);
177
+ async function execute$2(args, strapi) {
178
+ const validatedArgs = GetTranscriptSchema.parse(args);
297
179
  const {
298
180
  videoId: videoIdOrUrl,
299
181
  includeFullTranscript,
@@ -316,20 +198,9 @@ async function handleGetTranscript(strapi, args) {
316
198
  const transcript2 = await service2.findTranscript(videoId);
317
199
  if (!transcript2) {
318
200
  return {
319
- content: [
320
- {
321
- type: "text",
322
- text: JSON.stringify(
323
- {
324
- error: true,
325
- message: `No transcript found for video ID: ${videoId}. Use fetch_transcript to fetch it from YouTube first.`,
326
- videoId
327
- },
328
- null,
329
- 2
330
- )
331
- }
332
- ]
201
+ error: true,
202
+ message: `No transcript found for video ID: ${videoId}. Use fetchTranscript to fetch it from YouTube first.`,
203
+ videoId
333
204
  };
334
205
  }
335
206
  const timecodes = transcript2.transcriptWithTimeCodes || [];
@@ -395,7 +266,7 @@ async function handleGetTranscript(strapi, args) {
395
266
  response.transcriptWithTimeCodes = timecodes;
396
267
  }
397
268
  if (includeFullTranscript && fullText.length > maxFullTranscriptLength) {
398
- response.warning = "Full transcript included. For long videos, consider using chunkIndex, startTime/endTime, or search_transcript to reduce response size.";
269
+ response.warning = "Full transcript included. For long videos, consider using chunkIndex, startTime/endTime, or searchTranscript to reduce response size.";
399
270
  } else if (fullText.length <= maxFullTranscriptLength) {
400
271
  response.note = "Full transcript auto-loaded (fits within context limit).";
401
272
  }
@@ -405,42 +276,19 @@ async function handleGetTranscript(strapi, args) {
405
276
  response.isLargeTranscript = true;
406
277
  response.usage = {
407
278
  fullTranscript: "Set includeFullTranscript: true to get complete text (warning: may exceed context)",
408
- search: "Use search_transcript to find relevant portions by keyword (recommended for large transcripts)",
279
+ search: "Use searchTranscript to find relevant portions by keyword (recommended for large transcripts)",
409
280
  timeRange: "Use startTime and endTime (in seconds) to get a specific portion",
410
281
  pagination: `Use chunkIndex (0-${totalChunks - 1}) to paginate through ${chunkSizeSeconds}s chunks`
411
282
  };
412
283
  }
413
- return {
414
- content: [
415
- {
416
- type: "text",
417
- text: JSON.stringify(response, null, 2)
418
- }
419
- ]
420
- };
284
+ return response;
421
285
  }
422
- const searchTranscriptTool = {
423
- name: "search_transcript",
424
- description: "Search within a saved transcript using BM25 scoring. Returns the most relevant segments matching your query with timestamps. Use this to find specific content in long videos without loading the entire transcript.",
425
- inputSchema: {
426
- type: "object",
427
- properties: {
428
- videoId: {
429
- type: "string",
430
- description: 'YouTube video ID (e.g., "dQw4w9WgXcQ") or full YouTube URL'
431
- },
432
- query: {
433
- type: "string",
434
- description: "Search query - keywords or phrases to find in the transcript"
435
- },
436
- maxResults: {
437
- type: "number",
438
- description: "Maximum number of results to return (default: 5, max: 20)",
439
- default: 5
440
- }
441
- },
442
- required: ["videoId", "query"]
443
- }
286
+ const getTranscriptTool = {
287
+ name: "getTranscript",
288
+ description: "Get a saved transcript by YouTube video ID. Returns metadata and preview by default. Use parameters to get full content or specific time ranges to avoid context overflow.",
289
+ schema: GetTranscriptSchema,
290
+ execute: execute$2,
291
+ publicSafe: true
444
292
  };
445
293
  function tokenize(text) {
446
294
  return text.toLowerCase().replace(/[^\w\s]/g, " ").split(/\s+/).filter((word) => word.length > 1);
@@ -520,8 +368,8 @@ function createSegments(timecodes, segmentDurationMs) {
520
368
  }
521
369
  return segments;
522
370
  }
523
- async function handleSearchTranscript(strapi, args) {
524
- const validatedArgs = validateToolInput("search_transcript", args);
371
+ async function execute$1(args, strapi) {
372
+ const validatedArgs = SearchTranscriptSchema.parse(args);
525
373
  const { videoId: videoIdOrUrl, query, maxResults: maxResultsInput } = validatedArgs;
526
374
  const pluginConfig = await strapi.config.get("plugin::yt-transcript-strapi-plugin");
527
375
  const segmentSeconds = pluginConfig?.searchSegmentSeconds || 30;
@@ -534,77 +382,33 @@ async function handleSearchTranscript(strapi, args) {
534
382
  const transcript2 = await service2.findTranscript(videoId);
535
383
  if (!transcript2) {
536
384
  return {
537
- content: [
538
- {
539
- type: "text",
540
- text: JSON.stringify(
541
- {
542
- error: true,
543
- message: `No transcript found for video ID: ${videoId}. Use fetch_transcript to fetch it from YouTube first.`,
544
- videoId
545
- },
546
- null,
547
- 2
548
- )
549
- }
550
- ]
385
+ error: true,
386
+ message: `No transcript found for video ID: ${videoId}. Use fetchTranscript to fetch it from YouTube first.`,
387
+ videoId
551
388
  };
552
389
  }
553
390
  const timecodes = transcript2.transcriptWithTimeCodes || [];
554
391
  if (timecodes.length === 0) {
555
392
  return {
556
- content: [
557
- {
558
- type: "text",
559
- text: JSON.stringify(
560
- {
561
- error: true,
562
- message: "Transcript has no timecode data for searching.",
563
- videoId
564
- },
565
- null,
566
- 2
567
- )
568
- }
569
- ]
393
+ error: true,
394
+ message: "Transcript has no timecode data for searching.",
395
+ videoId
570
396
  };
571
397
  }
572
398
  const segments = createSegments(timecodes, segmentSeconds * 1e3);
573
399
  if (segments.length === 0) {
574
400
  return {
575
- content: [
576
- {
577
- type: "text",
578
- text: JSON.stringify(
579
- {
580
- error: true,
581
- message: "Could not create searchable segments from transcript.",
582
- videoId
583
- },
584
- null,
585
- 2
586
- )
587
- }
588
- ]
401
+ error: true,
402
+ message: "Could not create searchable segments from transcript.",
403
+ videoId
589
404
  };
590
405
  }
591
406
  const queryTokens = tokenize(query);
592
407
  if (queryTokens.length === 0) {
593
408
  return {
594
- content: [
595
- {
596
- type: "text",
597
- text: JSON.stringify(
598
- {
599
- error: true,
600
- message: "Query is empty or contains only stop words.",
601
- query
602
- },
603
- null,
604
- 2
605
- )
606
- }
607
- ]
409
+ error: true,
410
+ message: "Query is empty or contains only stop words.",
411
+ query
608
412
  };
609
413
  }
610
414
  const vocabulary = new Set(queryTokens);
@@ -616,74 +420,29 @@ async function handleSearchTranscript(strapi, args) {
616
420
  }));
617
421
  const results = scoredSegments.filter((seg) => seg.score > 0).sort((a, b) => b.score - a.score).slice(0, maxResults);
618
422
  return {
619
- content: [
620
- {
621
- type: "text",
622
- text: JSON.stringify(
623
- {
624
- videoId: transcript2.videoId,
625
- title: transcript2.title,
626
- query,
627
- totalSegments: segments.length,
628
- matchingResults: results.length,
629
- results: results.map((r) => ({
630
- text: r.text,
631
- startTime: r.startTime,
632
- endTime: r.endTime,
633
- timeRange: `${r.startFormatted} - ${r.endFormatted}`,
634
- score: Math.round(r.score * 100) / 100
635
- })),
636
- usage: results.length > 0 ? `Use get_transcript with startTime: ${results[0].startTime} and endTime: ${results[0].endTime} to get full context for the top result.` : "No matches found. Try different keywords."
637
- },
638
- null,
639
- 2
640
- )
641
- }
642
- ]
423
+ videoId: transcript2.videoId,
424
+ title: transcript2.title,
425
+ query,
426
+ totalSegments: segments.length,
427
+ matchingResults: results.length,
428
+ results: results.map((r) => ({
429
+ text: r.text,
430
+ startTime: r.startTime,
431
+ endTime: r.endTime,
432
+ timeRange: `${r.startFormatted} - ${r.endFormatted}`,
433
+ score: Math.round(r.score * 100) / 100
434
+ })),
435
+ usage: results.length > 0 ? `Use getTranscript with startTime: ${results[0].startTime} and endTime: ${results[0].endTime} to get full context for the top result.` : "No matches found. Try different keywords."
643
436
  };
644
437
  }
645
- const TRANSCRIPT_PREVIEW_LENGTH = 244;
646
- const findTranscriptsTool = {
647
- name: "find_transcripts",
648
- description: "Search and filter transcripts based on query criteria. Returns multiple matching transcripts with truncated previews (244 chars). Use get_transcript for full content. Supports filtering by title, videoId, and full-text search.",
649
- inputSchema: {
650
- type: "object",
651
- properties: {
652
- query: {
653
- type: "string",
654
- description: "Search query to match against title or transcript content"
655
- },
656
- videoId: {
657
- type: "string",
658
- description: "Filter by specific video ID (partial match supported)"
659
- },
660
- title: {
661
- type: "string",
662
- description: "Filter by title (partial match, case-insensitive)"
663
- },
664
- includeFullContent: {
665
- type: "boolean",
666
- description: "Set to true to include full transcript content. Default: false. Warning: may cause context overflow with multiple results."
667
- },
668
- page: {
669
- type: "number",
670
- description: "Page number (starts at 1)",
671
- default: 1
672
- },
673
- pageSize: {
674
- type: "number",
675
- description: "Number of items per page (max 100)",
676
- default: 25
677
- },
678
- sort: {
679
- type: "string",
680
- description: 'Sort order (e.g., "createdAt:desc", "title:asc")',
681
- default: "createdAt:desc"
682
- }
683
- },
684
- required: []
685
- }
438
+ const searchTranscriptTool = {
439
+ name: "searchTranscript",
440
+ description: "Search within a saved transcript using BM25 scoring. Returns the most relevant segments matching your query with timestamps. Use this to find specific content in long videos without loading the entire transcript.",
441
+ schema: SearchTranscriptSchema,
442
+ execute: execute$1,
443
+ publicSafe: true
686
444
  };
445
+ const TRANSCRIPT_PREVIEW_LENGTH = 244;
687
446
  function truncateText(text, maxLength) {
688
447
  if (!text) return null;
689
448
  if (text.length <= maxLength) return text;
@@ -695,8 +454,8 @@ function truncateTranscripts(transcripts) {
695
454
  fullTranscript: truncateText(transcript2.fullTranscript, TRANSCRIPT_PREVIEW_LENGTH)
696
455
  }));
697
456
  }
698
- async function handleFindTranscripts(strapi, args) {
699
- const validatedArgs = validateToolInput("find_transcripts", args);
457
+ async function execute(args, strapi) {
458
+ const validatedArgs = FindTranscriptsSchema.parse(args);
700
459
  const { query, videoId, title, includeFullContent, page, pageSize, sort } = validatedArgs;
701
460
  const start = (page - 1) * pageSize;
702
461
  const filters = {};
@@ -724,39 +483,231 @@ async function handleFindTranscripts(strapi, args) {
724
483
  });
725
484
  const total = allMatching.length;
726
485
  const processedTranscripts = includeFullContent ? transcripts : truncateTranscripts(transcripts);
486
+ return {
487
+ data: processedTranscripts,
488
+ pagination: {
489
+ page,
490
+ pageSize,
491
+ total,
492
+ pageCount: Math.ceil(total / pageSize)
493
+ },
494
+ filters: {
495
+ query: query || null,
496
+ videoId: videoId || null,
497
+ title: title || null
498
+ },
499
+ ...!includeFullContent && { note: "Transcript content truncated to 244 chars. Use getTranscript for full content or set includeFullContent=true." }
500
+ };
501
+ }
502
+ const findTranscriptsTool = {
503
+ name: "findTranscripts",
504
+ description: "Search and filter transcripts based on query criteria. Returns multiple matching transcripts with truncated previews (244 chars). Use getTranscript for full content. Supports filtering by title, videoId, and full-text search.",
505
+ schema: FindTranscriptsSchema,
506
+ execute,
507
+ publicSafe: true
508
+ };
509
+ const tools$1 = [
510
+ fetchTranscriptTool,
511
+ listTranscriptsTool,
512
+ getTranscriptTool,
513
+ searchTranscriptTool,
514
+ findTranscriptsTool
515
+ ];
516
+ const fetchTranscriptToolMcp = {
517
+ name: "fetch_transcript",
518
+ description: fetchTranscriptTool.description,
519
+ inputSchema: {
520
+ type: "object",
521
+ properties: {
522
+ videoId: {
523
+ type: "string",
524
+ description: 'YouTube video ID (e.g., "dQw4w9WgXcQ") or full YouTube URL'
525
+ }
526
+ },
527
+ required: ["videoId"]
528
+ }
529
+ };
530
+ async function handleFetchTranscript(strapi, args) {
531
+ const result = await fetchTranscriptTool.execute(args, strapi);
727
532
  return {
728
533
  content: [
729
534
  {
730
535
  type: "text",
731
- text: JSON.stringify(
732
- {
733
- data: processedTranscripts,
734
- pagination: {
735
- page,
736
- pageSize,
737
- total,
738
- pageCount: Math.ceil(total / pageSize)
739
- },
740
- filters: {
741
- query: query || null,
742
- videoId: videoId || null,
743
- title: title || null
744
- },
745
- ...!includeFullContent && { note: "Transcript content truncated to 244 chars. Use get_transcript for full content or set includeFullContent=true." }
746
- },
747
- null,
748
- 2
749
- )
536
+ text: JSON.stringify(result, null, 2)
537
+ }
538
+ ]
539
+ };
540
+ }
541
+ const listTranscriptsToolMcp = {
542
+ name: "list_transcripts",
543
+ description: listTranscriptsTool.description,
544
+ inputSchema: {
545
+ type: "object",
546
+ properties: {
547
+ page: {
548
+ type: "number",
549
+ description: "Page number (starts at 1)"
550
+ },
551
+ pageSize: {
552
+ type: "number",
553
+ description: "Number of items per page (max 100)"
554
+ },
555
+ sort: {
556
+ type: "string",
557
+ description: 'Sort order (e.g., "createdAt:desc", "title:asc")'
558
+ }
559
+ },
560
+ required: []
561
+ }
562
+ };
563
+ async function handleListTranscripts(strapi, args) {
564
+ const result = await listTranscriptsTool.execute(args, strapi);
565
+ return {
566
+ content: [
567
+ {
568
+ type: "text",
569
+ text: JSON.stringify(result, null, 2)
570
+ }
571
+ ]
572
+ };
573
+ }
574
+ const getTranscriptToolMcp = {
575
+ name: "get_transcript",
576
+ description: getTranscriptTool.description,
577
+ inputSchema: {
578
+ type: "object",
579
+ properties: {
580
+ videoId: {
581
+ type: "string",
582
+ description: 'YouTube video ID (e.g., "dQw4w9WgXcQ") or full YouTube URL'
583
+ },
584
+ includeFullTranscript: {
585
+ type: "boolean",
586
+ description: "Include the complete transcript text. Warning: may cause context overflow for long videos. Default: false"
587
+ },
588
+ includeTimecodes: {
589
+ type: "boolean",
590
+ description: "Include the transcript with timecodes array. Warning: significantly increases response size. Default: false"
591
+ },
592
+ startTime: {
593
+ type: "number",
594
+ description: "Start time in seconds for fetching a specific portion of the transcript"
595
+ },
596
+ endTime: {
597
+ type: "number",
598
+ description: "End time in seconds for fetching a specific portion of the transcript"
599
+ },
600
+ chunkIndex: {
601
+ type: "number",
602
+ description: "Chunk index (0-based) when paginating through transcript. Use with chunkSize to paginate through long videos."
603
+ },
604
+ chunkSize: {
605
+ type: "number",
606
+ description: "Chunk size in seconds. Overrides config default. Use with chunkIndex for pagination."
607
+ }
608
+ },
609
+ required: ["videoId"]
610
+ }
611
+ };
612
+ async function handleGetTranscript(strapi, args) {
613
+ const result = await getTranscriptTool.execute(args, strapi);
614
+ return {
615
+ content: [
616
+ {
617
+ type: "text",
618
+ text: JSON.stringify(result, null, 2)
619
+ }
620
+ ]
621
+ };
622
+ }
623
+ const searchTranscriptToolMcp = {
624
+ name: "search_transcript",
625
+ description: searchTranscriptTool.description,
626
+ inputSchema: {
627
+ type: "object",
628
+ properties: {
629
+ videoId: {
630
+ type: "string",
631
+ description: 'YouTube video ID (e.g., "dQw4w9WgXcQ") or full YouTube URL'
632
+ },
633
+ query: {
634
+ type: "string",
635
+ description: "Search query - keywords or phrases to find in the transcript"
636
+ },
637
+ maxResults: {
638
+ type: "number",
639
+ description: "Maximum number of results to return (default: 5, max: 20)"
640
+ }
641
+ },
642
+ required: ["videoId", "query"]
643
+ }
644
+ };
645
+ async function handleSearchTranscript(strapi, args) {
646
+ const result = await searchTranscriptTool.execute(args, strapi);
647
+ return {
648
+ content: [
649
+ {
650
+ type: "text",
651
+ text: JSON.stringify(result, null, 2)
652
+ }
653
+ ]
654
+ };
655
+ }
656
+ const findTranscriptsToolMcp = {
657
+ name: "find_transcripts",
658
+ description: findTranscriptsTool.description,
659
+ inputSchema: {
660
+ type: "object",
661
+ properties: {
662
+ query: {
663
+ type: "string",
664
+ description: "Search query to match against title or transcript content"
665
+ },
666
+ videoId: {
667
+ type: "string",
668
+ description: "Filter by specific video ID (partial match supported)"
669
+ },
670
+ title: {
671
+ type: "string",
672
+ description: "Filter by title (partial match, case-insensitive)"
673
+ },
674
+ includeFullContent: {
675
+ type: "boolean",
676
+ description: "Set to true to include full transcript content. Default: false. Warning: may cause context overflow with multiple results."
677
+ },
678
+ page: {
679
+ type: "number",
680
+ description: "Page number (starts at 1)"
681
+ },
682
+ pageSize: {
683
+ type: "number",
684
+ description: "Number of items per page (max 100)"
685
+ },
686
+ sort: {
687
+ type: "string",
688
+ description: 'Sort order (e.g., "createdAt:desc", "title:asc")'
689
+ }
690
+ },
691
+ required: []
692
+ }
693
+ };
694
+ async function handleFindTranscripts(strapi, args) {
695
+ const result = await findTranscriptsTool.execute(args, strapi);
696
+ return {
697
+ content: [
698
+ {
699
+ type: "text",
700
+ text: JSON.stringify(result, null, 2)
750
701
  }
751
702
  ]
752
703
  };
753
704
  }
754
705
  const tools = [
755
- fetchTranscriptTool,
756
- listTranscriptsTool,
757
- getTranscriptTool,
758
- searchTranscriptTool,
759
- findTranscriptsTool
706
+ fetchTranscriptToolMcp,
707
+ listTranscriptsToolMcp,
708
+ getTranscriptToolMcp,
709
+ searchTranscriptToolMcp,
710
+ findTranscriptsToolMcp
760
711
  ];
761
712
  const toolHandlers = {
762
713
  fetch_transcript: handleFetchTranscript,
@@ -1451,8 +1402,14 @@ const service = ({ strapi }) => ({
1451
1402
  return transcriptData;
1452
1403
  }
1453
1404
  });
1405
+ const aiTools = ({ strapi }) => ({
1406
+ getTools() {
1407
+ return tools$1;
1408
+ }
1409
+ });
1454
1410
  const services = {
1455
- service
1411
+ service,
1412
+ "ai-tools": aiTools
1456
1413
  };
1457
1414
  const index = {
1458
1415
  register,
@@ -1469,4 +1426,3 @@ const index = {
1469
1426
  export {
1470
1427
  index as default
1471
1428
  };
1472
- //# sourceMappingURL=index.mjs.map