yt-transcript-strapi-plugin 0.0.20 → 0.0.22

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.
Files changed (39) hide show
  1. package/dist/server/index.js +49 -125
  2. package/dist/server/index.mjs +49 -125
  3. package/dist/server/src/content-types/index.d.ts +0 -3
  4. package/dist/server/src/content-types/transcript/index.d.ts +0 -3
  5. package/dist/server/src/index.d.ts +0 -4
  6. package/dist/server/src/mcp/schemas/index.d.ts +0 -6
  7. package/dist/server/src/mcp/tools/fetch-transcript.d.ts +0 -5
  8. package/dist/server/src/mcp/tools/index.d.ts +13 -13
  9. package/dist/server/src/services/index.d.ts +0 -1
  10. package/dist/server/src/services/service.d.ts +0 -2
  11. package/node_modules/express/node_modules/media-typer/HISTORY.md +50 -0
  12. package/node_modules/express/node_modules/media-typer/LICENSE +22 -0
  13. package/node_modules/express/node_modules/media-typer/README.md +93 -0
  14. package/node_modules/express/node_modules/media-typer/index.js +143 -0
  15. package/node_modules/express/node_modules/media-typer/package.json +33 -0
  16. package/node_modules/express/node_modules/type-is/HISTORY.md +292 -0
  17. package/node_modules/express/node_modules/type-is/LICENSE +23 -0
  18. package/node_modules/express/node_modules/type-is/README.md +198 -0
  19. package/node_modules/express/node_modules/type-is/index.js +250 -0
  20. package/node_modules/express/node_modules/type-is/package.json +47 -0
  21. package/package.json +1 -5
  22. package/dist/server/src/utils/openai.d.ts +0 -9
  23. package/node_modules/which/CHANGELOG.md +0 -166
  24. /package/node_modules/{media-typer → body-parser/node_modules/media-typer}/HISTORY.md +0 -0
  25. /package/node_modules/{media-typer → body-parser/node_modules/media-typer}/LICENSE +0 -0
  26. /package/node_modules/{media-typer → body-parser/node_modules/media-typer}/README.md +0 -0
  27. /package/node_modules/{media-typer → body-parser/node_modules/media-typer}/index.js +0 -0
  28. /package/node_modules/{media-typer → body-parser/node_modules/media-typer}/package.json +0 -0
  29. /package/node_modules/{type-is → body-parser}/node_modules/mime-types/HISTORY.md +0 -0
  30. /package/node_modules/{type-is → body-parser}/node_modules/mime-types/LICENSE +0 -0
  31. /package/node_modules/{type-is → body-parser}/node_modules/mime-types/README.md +0 -0
  32. /package/node_modules/{type-is → body-parser}/node_modules/mime-types/index.js +0 -0
  33. /package/node_modules/{type-is → body-parser}/node_modules/mime-types/mimeScore.js +0 -0
  34. /package/node_modules/{type-is → body-parser}/node_modules/mime-types/package.json +0 -0
  35. /package/node_modules/{type-is → body-parser/node_modules/type-is}/HISTORY.md +0 -0
  36. /package/node_modules/{type-is → body-parser/node_modules/type-is}/LICENSE +0 -0
  37. /package/node_modules/{type-is → body-parser/node_modules/type-is}/README.md +0 -0
  38. /package/node_modules/{type-is → body-parser/node_modules/type-is}/index.js +0 -0
  39. /package/node_modules/{type-is → body-parser/node_modules/type-is}/package.json +0 -0
@@ -4,14 +4,10 @@ const types_js = require("@modelcontextprotocol/sdk/types.js");
4
4
  const zod = require("zod");
5
5
  const node_crypto = require("node:crypto");
6
6
  const streamableHttp_js = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
7
- const textsplitters = require("@langchain/textsplitters");
8
- const prompts = require("@langchain/core/prompts");
9
- const openai = require("@langchain/openai");
10
7
  const youtubei_js = require("youtubei.js");
11
8
  const undici = require("undici");
12
9
  const FetchTranscriptSchema = zod.z.object({
13
- videoId: zod.z.string().min(1, "Video ID or URL is required"),
14
- generateReadable: zod.z.boolean().optional().default(false)
10
+ videoId: zod.z.string().min(1, "Video ID or URL is required")
15
11
  });
16
12
  const ListTranscriptsSchema = zod.z.object({
17
13
  page: zod.z.number().int().min(1).optional().default(1),
@@ -68,31 +64,26 @@ function extractYouTubeID(urlOrID) {
68
64
  }
69
65
  const fetchTranscriptTool = {
70
66
  name: "fetch_transcript",
71
- description: "Fetch a transcript from YouTube for a given video ID or URL. Optionally generates a human-readable version using AI. The transcript is saved to the database for future retrieval.",
67
+ description: "Fetch a transcript from YouTube for a given video ID or URL. The transcript is saved to the database for future retrieval.",
72
68
  inputSchema: {
73
69
  type: "object",
74
70
  properties: {
75
71
  videoId: {
76
72
  type: "string",
77
73
  description: 'YouTube video ID (e.g., "dQw4w9WgXcQ") or full YouTube URL'
78
- },
79
- generateReadable: {
80
- type: "boolean",
81
- description: "If true, uses AI to add punctuation and formatting to make the transcript more readable. Requires OpenAI API key configuration.",
82
- default: false
83
74
  }
84
75
  },
85
76
  required: ["videoId"]
86
77
  }
87
78
  };
88
- async function handleFetchTranscript(strapi2, args) {
79
+ async function handleFetchTranscript(strapi, args) {
89
80
  const validatedArgs = validateToolInput("fetch_transcript", args);
90
- const { videoId: videoIdOrUrl, generateReadable } = validatedArgs;
81
+ const { videoId: videoIdOrUrl } = validatedArgs;
91
82
  const videoId = extractYouTubeID(videoIdOrUrl);
92
83
  if (!videoId) {
93
84
  throw new Error(`Invalid YouTube video ID or URL: "${videoIdOrUrl}". Please provide a valid 11-character video ID or YouTube URL.`);
94
85
  }
95
- const service2 = strapi2.plugin("yt-transcript-strapi-plugin").service("service");
86
+ const service2 = strapi.plugin("yt-transcript-strapi-plugin").service("service");
96
87
  const existingTranscript = await service2.findTranscript(videoId);
97
88
  if (existingTranscript) {
98
89
  return {
@@ -122,14 +113,6 @@ async function handleFetchTranscript(strapi2, args) {
122
113
  fullTranscript: transcriptData.fullTranscript,
123
114
  transcriptWithTimeCodes: transcriptData.transcriptWithTimeCodes
124
115
  };
125
- if (generateReadable && transcriptData.fullTranscript) {
126
- try {
127
- const readableTranscript = await service2.generateHumanReadableTranscript(transcriptData.fullTranscript);
128
- payload.readableTranscript = readableTranscript;
129
- } catch (error) {
130
- strapi2.log.warn("[yt-transcript-mcp] Failed to generate readable transcript:", error);
131
- }
132
- }
133
116
  const savedTranscript = await service2.saveTranscript(payload);
134
117
  return {
135
118
  content: [
@@ -173,17 +156,17 @@ const listTranscriptsTool = {
173
156
  required: []
174
157
  }
175
158
  };
176
- async function handleListTranscripts(strapi2, args) {
159
+ async function handleListTranscripts(strapi, args) {
177
160
  const validatedArgs = validateToolInput("list_transcripts", args);
178
161
  const { page, pageSize, sort } = validatedArgs;
179
162
  const start = (page - 1) * pageSize;
180
- const transcripts = await strapi2.documents("plugin::yt-transcript-strapi-plugin.transcript").findMany({
163
+ const transcripts = await strapi.documents("plugin::yt-transcript-strapi-plugin.transcript").findMany({
181
164
  sort,
182
165
  limit: pageSize,
183
166
  start,
184
167
  fields: ["id", "documentId", "title", "videoId", "createdAt", "updatedAt"]
185
168
  });
186
- const allTranscripts = await strapi2.documents("plugin::yt-transcript-strapi-plugin.transcript").findMany({});
169
+ const allTranscripts = await strapi.documents("plugin::yt-transcript-strapi-plugin.transcript").findMany({});
187
170
  const total = allTranscripts.length;
188
171
  return {
189
172
  content: [
@@ -220,14 +203,14 @@ const getTranscriptTool = {
220
203
  required: ["videoId"]
221
204
  }
222
205
  };
223
- async function handleGetTranscript(strapi2, args) {
206
+ async function handleGetTranscript(strapi, args) {
224
207
  const validatedArgs = validateToolInput("get_transcript", args);
225
208
  const { videoId: videoIdOrUrl } = validatedArgs;
226
209
  const videoId = extractYouTubeID(videoIdOrUrl);
227
210
  if (!videoId) {
228
211
  throw new Error(`Invalid YouTube video ID or URL: "${videoIdOrUrl}". Please provide a valid 11-character video ID or YouTube URL.`);
229
212
  }
230
- const service2 = strapi2.plugin("yt-transcript-strapi-plugin").service("service");
213
+ const service2 = strapi.plugin("yt-transcript-strapi-plugin").service("service");
231
214
  const transcript2 = await service2.findTranscript(videoId);
232
215
  if (!transcript2) {
233
216
  return {
@@ -312,11 +295,10 @@ function truncateText(text, maxLength) {
312
295
  function truncateTranscripts(transcripts) {
313
296
  return transcripts.map((transcript2) => ({
314
297
  ...transcript2,
315
- fullTranscript: truncateText(transcript2.fullTranscript, TRANSCRIPT_PREVIEW_LENGTH),
316
- readableTranscript: truncateText(transcript2.readableTranscript, TRANSCRIPT_PREVIEW_LENGTH)
298
+ fullTranscript: truncateText(transcript2.fullTranscript, TRANSCRIPT_PREVIEW_LENGTH)
317
299
  }));
318
300
  }
319
- async function handleFindTranscripts(strapi2, args) {
301
+ async function handleFindTranscripts(strapi, args) {
320
302
  const validatedArgs = validateToolInput("find_transcripts", args);
321
303
  const { query, videoId, title, includeFullContent, page, pageSize, sort } = validatedArgs;
322
304
  const start = (page - 1) * pageSize;
@@ -331,17 +313,16 @@ async function handleFindTranscripts(strapi2, args) {
331
313
  filters.$or = [
332
314
  { title: { $containsi: query } },
333
315
  { videoId: { $containsi: query } },
334
- { fullTranscript: { $containsi: query } },
335
- { readableTranscript: { $containsi: query } }
316
+ { fullTranscript: { $containsi: query } }
336
317
  ];
337
318
  }
338
- const transcripts = await strapi2.documents("plugin::yt-transcript-strapi-plugin.transcript").findMany({
319
+ const transcripts = await strapi.documents("plugin::yt-transcript-strapi-plugin.transcript").findMany({
339
320
  filters,
340
321
  sort,
341
322
  limit: pageSize,
342
323
  start
343
324
  });
344
- const allMatching = await strapi2.documents("plugin::yt-transcript-strapi-plugin.transcript").findMany({
325
+ const allMatching = await strapi.documents("plugin::yt-transcript-strapi-plugin.transcript").findMany({
345
326
  filters
346
327
  });
347
328
  const total = allMatching.length;
@@ -385,7 +366,7 @@ const toolHandlers = {
385
366
  get_transcript: handleGetTranscript,
386
367
  find_transcripts: handleFindTranscripts
387
368
  };
388
- async function handleToolCall(strapi2, request) {
369
+ async function handleToolCall(strapi, request) {
389
370
  const { name, arguments: args } = request.params;
390
371
  const handler = toolHandlers[name];
391
372
  if (!handler) {
@@ -393,13 +374,13 @@ async function handleToolCall(strapi2, request) {
393
374
  }
394
375
  const startTime = Date.now();
395
376
  try {
396
- const result = await handler(strapi2, args || {});
377
+ const result = await handler(strapi, args || {});
397
378
  const duration = Date.now() - startTime;
398
- strapi2.log.debug(`[yt-transcript-mcp] Tool ${name} executed successfully in ${duration}ms`);
379
+ strapi.log.debug(`[yt-transcript-mcp] Tool ${name} executed successfully in ${duration}ms`);
399
380
  return result;
400
381
  } catch (error) {
401
382
  const duration = Date.now() - startTime;
402
- strapi2.log.error(`[yt-transcript-mcp] Tool ${name} failed after ${duration}ms`, {
383
+ strapi.log.error(`[yt-transcript-mcp] Tool ${name} failed after ${duration}ms`, {
403
384
  error: error instanceof Error ? error.message : String(error)
404
385
  });
405
386
  return {
@@ -420,7 +401,7 @@ async function handleToolCall(strapi2, request) {
420
401
  };
421
402
  }
422
403
  }
423
- function createMcpServer(strapi2) {
404
+ function createMcpServer(strapi) {
424
405
  const server = new index_js.Server(
425
406
  {
426
407
  name: "yt-transcript-mcp",
@@ -433,28 +414,28 @@ function createMcpServer(strapi2) {
433
414
  }
434
415
  );
435
416
  server.setRequestHandler(types_js.ListToolsRequestSchema, async () => {
436
- strapi2.log.debug("[yt-transcript-mcp] Listing tools");
417
+ strapi.log.debug("[yt-transcript-mcp] Listing tools");
437
418
  return { tools };
438
419
  });
439
420
  server.setRequestHandler(types_js.CallToolRequestSchema, async (request) => {
440
- strapi2.log.debug(`[yt-transcript-mcp] Tool call: ${request.params.name}`);
441
- return handleToolCall(strapi2, request);
421
+ strapi.log.debug(`[yt-transcript-mcp] Tool call: ${request.params.name}`);
422
+ return handleToolCall(strapi, request);
442
423
  });
443
- strapi2.log.info("[yt-transcript-mcp] MCP server created with tools:", {
424
+ strapi.log.info("[yt-transcript-mcp] MCP server created with tools:", {
444
425
  tools: tools.map((t) => t.name)
445
426
  });
446
427
  return server;
447
428
  }
448
- const bootstrap = async ({ strapi: strapi2 }) => {
449
- const plugin = strapi2.plugin("yt-transcript-strapi-plugin");
450
- plugin.createMcpServer = () => createMcpServer(strapi2);
429
+ const bootstrap = async ({ strapi }) => {
430
+ const plugin = strapi.plugin("yt-transcript-strapi-plugin");
431
+ plugin.createMcpServer = () => createMcpServer(strapi);
451
432
  plugin.sessions = /* @__PURE__ */ new Map();
452
- strapi2.log.info("[yt-transcript-mcp] MCP plugin initialized");
453
- strapi2.log.info("[yt-transcript-mcp] MCP endpoint available at: /api/yt-transcript-strapi-plugin/mcp");
433
+ strapi.log.info("[yt-transcript-mcp] MCP plugin initialized");
434
+ strapi.log.info("[yt-transcript-mcp] MCP endpoint available at: /api/yt-transcript-strapi-plugin/mcp");
454
435
  };
455
- const destroy = ({ strapi: strapi2 }) => {
436
+ const destroy = ({ strapi }) => {
456
437
  };
457
- const register = ({ strapi: strapi2 }) => {
438
+ const register = ({ strapi }) => {
458
439
  };
459
440
  const config = {
460
441
  default: {
@@ -513,9 +494,6 @@ const attributes = {
513
494
  },
514
495
  transcriptWithTimeCodes: {
515
496
  type: "json"
516
- },
517
- readableTranscript: {
518
- type: "richtext"
519
497
  }
520
498
  };
521
499
  const schema = {
@@ -532,41 +510,34 @@ const transcript = {
532
510
  const contentTypes = {
533
511
  transcript
534
512
  };
535
- const controller = ({ strapi: strapi2 }) => ({
513
+ const controller = ({ strapi }) => ({
536
514
  async getTranscript(ctx) {
537
515
  const videoId = extractYouTubeID(ctx.params.videoId);
538
516
  if (!videoId) {
539
517
  return ctx.body = { error: "Invalid YouTube URL or ID", data: null };
540
518
  }
541
- const found = await strapi2.plugin("yt-transcript-strapi-plugin").service("service").findTranscript(videoId);
519
+ const found = await strapi.plugin("yt-transcript-strapi-plugin").service("service").findTranscript(videoId);
542
520
  if (found) {
543
521
  return ctx.body = { data: found };
544
522
  }
545
- const transcriptData = await strapi2.plugin("yt-transcript-strapi-plugin").service("service").getTranscript(videoId);
546
- let readableTranscript = null;
547
- try {
548
- readableTranscript = await strapi2.plugin("yt-transcript-strapi-plugin").service("service").generateHumanReadableTranscript(transcriptData.fullTranscript);
549
- } catch (error) {
550
- strapi2.log.debug("[yt-transcript] Readable transcript generation skipped");
551
- }
523
+ const transcriptData = await strapi.plugin("yt-transcript-strapi-plugin").service("service").getTranscript(videoId);
552
524
  const payload = {
553
525
  videoId,
554
526
  title: transcriptData?.title || "No title found",
555
527
  fullTranscript: transcriptData?.fullTranscript,
556
- transcriptWithTimeCodes: transcriptData?.transcriptWithTimeCodes,
557
- readableTranscript
528
+ transcriptWithTimeCodes: transcriptData?.transcriptWithTimeCodes
558
529
  };
559
- const transcript2 = await strapi2.plugin("yt-transcript-strapi-plugin").service("service").saveTranscript(payload);
530
+ const transcript2 = await strapi.plugin("yt-transcript-strapi-plugin").service("service").saveTranscript(payload);
560
531
  ctx.body = { data: transcript2 };
561
532
  }
562
533
  });
563
- const mcpController = ({ strapi: strapi2 }) => ({
534
+ const mcpController = ({ strapi }) => ({
564
535
  /**
565
536
  * Handle MCP requests (POST, GET, DELETE)
566
537
  * Creates a new server+transport per session for proper isolation
567
538
  */
568
539
  async handle(ctx) {
569
- const plugin = strapi2.plugin("yt-transcript-strapi-plugin");
540
+ const plugin = strapi.plugin("yt-transcript-strapi-plugin");
570
541
  if (!plugin.createMcpServer) {
571
542
  ctx.status = 503;
572
543
  ctx.body = {
@@ -586,12 +557,12 @@ const mcpController = ({ strapi: strapi2 }) => ({
586
557
  await server.connect(transport);
587
558
  session = { server, transport, createdAt: Date.now() };
588
559
  plugin.sessions.set(sessionId, session);
589
- strapi2.log.debug(`[yt-transcript-mcp] New session created: ${sessionId}`);
560
+ strapi.log.debug(`[yt-transcript-mcp] New session created: ${sessionId}`);
590
561
  }
591
562
  await session.transport.handleRequest(ctx.req, ctx.res, ctx.request.body);
592
563
  ctx.respond = false;
593
564
  } catch (error) {
594
- strapi2.log.error("[yt-transcript-mcp] Error handling MCP request", {
565
+ strapi.log.error("[yt-transcript-mcp] Error handling MCP request", {
595
566
  error: error instanceof Error ? error.message : String(error),
596
567
  method: ctx.method,
597
568
  path: ctx.path
@@ -671,17 +642,8 @@ const routes = {
671
642
  routes: [...admin]
672
643
  }
673
644
  };
674
- async function initializeModel({
675
- openAIApiKey,
676
- model,
677
- temp
678
- }) {
679
- return new openai.ChatOpenAI({
680
- temperature: temp,
681
- openAIApiKey,
682
- modelName: model,
683
- maxTokens: 1e3
684
- });
645
+ function isRequestLike(input) {
646
+ return typeof input === "object" && input !== null && "url" in input && typeof input.url === "string" && "method" in input;
685
647
  }
686
648
  function createProxyFetch(proxyUrl) {
687
649
  if (!proxyUrl) {
@@ -689,7 +651,7 @@ function createProxyFetch(proxyUrl) {
689
651
  }
690
652
  const proxyAgent = new undici.ProxyAgent(proxyUrl);
691
653
  return async (input, init) => {
692
- if (input instanceof Request) {
654
+ if (isRequestLike(input)) {
693
655
  const url = input.url;
694
656
  return undici.fetch(url, {
695
657
  method: input.method,
@@ -699,7 +661,8 @@ function createProxyFetch(proxyUrl) {
699
661
  dispatcher: proxyAgent
700
662
  });
701
663
  }
702
- return undici.fetch(input, { ...init, dispatcher: proxyAgent });
664
+ const urlString = input instanceof URL ? input.toString() : input;
665
+ return undici.fetch(urlString, { ...init, dispatcher: proxyAgent });
703
666
  };
704
667
  }
705
668
  function decodeHtmlEntities(text) {
@@ -821,49 +784,14 @@ const fetchTranscript = async (videoId, options2) => {
821
784
  );
822
785
  }
823
786
  };
824
- async function processTextChunks(chunks, model) {
825
- const punctuationPrompt = prompts.PromptTemplate.fromTemplate(
826
- "Add proper punctuation and capitalization to the following text chunk:\n\n{chunk}"
827
- );
828
- const punctuationChain = punctuationPrompt.pipe(model);
829
- const processedChunks = await Promise.all(
830
- chunks.map(async (chunk) => {
831
- const result = await punctuationChain.invoke({ chunk });
832
- return result.content;
833
- })
834
- );
835
- return processedChunks.join(" ");
836
- }
837
- async function generateModifiedTranscript(rawTranscript) {
838
- const pluginSettings = await strapi.config.get(
839
- "plugin::yt-transcript-strapi-plugin"
840
- );
841
- if (!pluginSettings.openAIApiKey || !pluginSettings.model || !pluginSettings.temp || !pluginSettings.maxTokens) {
842
- throw new Error("Missing required configuration for YTTranscript");
843
- }
844
- const chatModel = await initializeModel({
845
- openAIApiKey: pluginSettings.openAIApiKey,
846
- model: pluginSettings.model,
847
- temp: pluginSettings.temp,
848
- maxTokens: pluginSettings.maxTokens
849
- });
850
- const splitter = new textsplitters.TokenTextSplitter({
851
- chunkSize: 1e3,
852
- chunkOverlap: 200
853
- });
854
- const transcriptChunks = await splitter.createDocuments([rawTranscript]);
855
- const chunkTexts = transcriptChunks.map((chunk) => chunk.pageContent);
856
- const modifiedTranscript = await processTextChunks(chunkTexts, chatModel);
857
- return modifiedTranscript;
858
- }
859
- const service = ({ strapi: strapi2 }) => ({
787
+ const service = ({ strapi }) => ({
860
788
  async getTranscript(identifier) {
861
789
  const youtubeIdRegex = /^[a-zA-Z0-9_-]{11}$/;
862
790
  const isValid = youtubeIdRegex.test(identifier);
863
791
  if (!isValid) {
864
792
  return { error: "Invalid video ID", data: null };
865
793
  }
866
- const pluginSettings = await strapi2.config.get(
794
+ const pluginSettings = await strapi.config.get(
867
795
  "plugin::yt-transcript-strapi-plugin"
868
796
  );
869
797
  const transcriptData = await fetchTranscript(identifier, {
@@ -876,20 +804,16 @@ const service = ({ strapi: strapi2 }) => ({
876
804
  };
877
805
  },
878
806
  async saveTranscript(payload) {
879
- return await strapi2.documents("plugin::yt-transcript-strapi-plugin.transcript").create({
807
+ return await strapi.documents("plugin::yt-transcript-strapi-plugin.transcript").create({
880
808
  data: payload
881
809
  });
882
810
  },
883
811
  async findTranscript(videoId) {
884
- const transcriptData = await strapi2.documents("plugin::yt-transcript-strapi-plugin.transcript").findFirst({
812
+ const transcriptData = await strapi.documents("plugin::yt-transcript-strapi-plugin.transcript").findFirst({
885
813
  filters: { videoId }
886
814
  });
887
815
  if (!transcriptData) return null;
888
816
  return transcriptData;
889
- },
890
- async generateHumanReadableTranscript(transcript2) {
891
- const modifiedTranscript = await generateModifiedTranscript(transcript2);
892
- return modifiedTranscript;
893
817
  }
894
818
  });
895
819
  const services = {