yt-transcript-strapi-plugin 0.0.21 → 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 +44 -124
  2. package/dist/server/index.mjs +44 -124
  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,18 +642,6 @@ 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
- });
685
- }
686
645
  function isRequestLike(input) {
687
646
  return typeof input === "object" && input !== null && "url" in input && typeof input.url === "string" && "method" in input;
688
647
  }
@@ -825,49 +784,14 @@ const fetchTranscript = async (videoId, options2) => {
825
784
  );
826
785
  }
827
786
  };
828
- async function processTextChunks(chunks, model) {
829
- const punctuationPrompt = prompts.PromptTemplate.fromTemplate(
830
- "Add proper punctuation and capitalization to the following text chunk:\n\n{chunk}"
831
- );
832
- const punctuationChain = punctuationPrompt.pipe(model);
833
- const processedChunks = await Promise.all(
834
- chunks.map(async (chunk) => {
835
- const result = await punctuationChain.invoke({ chunk });
836
- return result.content;
837
- })
838
- );
839
- return processedChunks.join(" ");
840
- }
841
- async function generateModifiedTranscript(rawTranscript) {
842
- const pluginSettings = await strapi.config.get(
843
- "plugin::yt-transcript-strapi-plugin"
844
- );
845
- if (!pluginSettings.openAIApiKey || !pluginSettings.model || !pluginSettings.temp || !pluginSettings.maxTokens) {
846
- throw new Error("Missing required configuration for YTTranscript");
847
- }
848
- const chatModel = await initializeModel({
849
- openAIApiKey: pluginSettings.openAIApiKey,
850
- model: pluginSettings.model,
851
- temp: pluginSettings.temp,
852
- maxTokens: pluginSettings.maxTokens
853
- });
854
- const splitter = new textsplitters.TokenTextSplitter({
855
- chunkSize: 1e3,
856
- chunkOverlap: 200
857
- });
858
- const transcriptChunks = await splitter.createDocuments([rawTranscript]);
859
- const chunkTexts = transcriptChunks.map((chunk) => chunk.pageContent);
860
- const modifiedTranscript = await processTextChunks(chunkTexts, chatModel);
861
- return modifiedTranscript;
862
- }
863
- const service = ({ strapi: strapi2 }) => ({
787
+ const service = ({ strapi }) => ({
864
788
  async getTranscript(identifier) {
865
789
  const youtubeIdRegex = /^[a-zA-Z0-9_-]{11}$/;
866
790
  const isValid = youtubeIdRegex.test(identifier);
867
791
  if (!isValid) {
868
792
  return { error: "Invalid video ID", data: null };
869
793
  }
870
- const pluginSettings = await strapi2.config.get(
794
+ const pluginSettings = await strapi.config.get(
871
795
  "plugin::yt-transcript-strapi-plugin"
872
796
  );
873
797
  const transcriptData = await fetchTranscript(identifier, {
@@ -880,20 +804,16 @@ const service = ({ strapi: strapi2 }) => ({
880
804
  };
881
805
  },
882
806
  async saveTranscript(payload) {
883
- return await strapi2.documents("plugin::yt-transcript-strapi-plugin.transcript").create({
807
+ return await strapi.documents("plugin::yt-transcript-strapi-plugin.transcript").create({
884
808
  data: payload
885
809
  });
886
810
  },
887
811
  async findTranscript(videoId) {
888
- 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({
889
813
  filters: { videoId }
890
814
  });
891
815
  if (!transcriptData) return null;
892
816
  return transcriptData;
893
- },
894
- async generateHumanReadableTranscript(transcript2) {
895
- const modifiedTranscript = await generateModifiedTranscript(transcript2);
896
- return modifiedTranscript;
897
817
  }
898
818
  });
899
819
  const services = {