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