docusaurus-plugin-mcp-server 0.8.0 → 0.10.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.
@@ -3,10 +3,10 @@
3
3
  var mcp_js = require('@modelcontextprotocol/sdk/server/mcp.js');
4
4
  var streamableHttp_js = require('@modelcontextprotocol/sdk/server/streamableHttp.js');
5
5
  var webStandardStreamableHttp_js = require('@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js');
6
- var zod = require('zod');
7
6
  var FlexSearch = require('flexsearch');
8
7
  var fs = require('fs-extra');
9
- require('http');
8
+ var zod = require('zod');
9
+ var http = require('http');
10
10
 
11
11
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
12
 
@@ -81,6 +81,8 @@ function searchIndex(index, docs, query, options = {}) {
81
81
  const doc = docs[docId];
82
82
  if (!doc) continue;
83
83
  results.push({
84
+ url: docId,
85
+ // docId is the full URL when indexed with baseUrl
84
86
  route: doc.route,
85
87
  title: doc.title,
86
88
  score,
@@ -188,16 +190,11 @@ var FlexSearchProvider = class {
188
190
  const limit = options?.limit ?? 5;
189
191
  return searchIndex(this.searchIndex, this.docs, query, { limit });
190
192
  }
191
- async getDocument(route) {
193
+ async getDocument(url) {
192
194
  if (!this.docs) {
193
195
  throw new Error("[FlexSearch] Provider not initialized");
194
196
  }
195
- if (this.docs[route]) {
196
- return this.docs[route];
197
- }
198
- const normalizedRoute = route.startsWith("/") ? route : `/${route}`;
199
- const withoutSlash = route.startsWith("/") ? route.slice(1) : route;
200
- return this.docs[normalizedRoute] ?? this.docs[withoutSlash] ?? null;
197
+ return this.docs[url] ?? null;
201
198
  }
202
199
  async healthCheck() {
203
200
  if (!this.isReady()) {
@@ -262,9 +259,16 @@ function isSearchProvider(obj) {
262
259
  const provider = obj;
263
260
  return typeof provider.name === "string" && typeof provider.initialize === "function" && typeof provider.isReady === "function" && typeof provider.search === "function";
264
261
  }
265
-
266
- // src/mcp/tools/docs-search.ts
267
- function formatSearchResults(results, baseUrl) {
262
+ var docsSearchInputSchema = {
263
+ query: zod.z.string().min(1).describe("The search query string"),
264
+ limit: zod.z.number().int().min(1).max(20).optional().default(5).describe("Maximum number of results to return (1-20, default: 5)")
265
+ };
266
+ var docsSearchTool = {
267
+ name: "docs_search",
268
+ description: "Search the documentation for relevant pages. Returns matching documents with URLs, snippets, and relevance scores. Use this to find information across all documentation.",
269
+ inputSchema: docsSearchInputSchema
270
+ };
271
+ function formatSearchResults(results) {
268
272
  if (results.length === 0) {
269
273
  return "No matching documents found.";
270
274
  }
@@ -274,24 +278,29 @@ function formatSearchResults(results, baseUrl) {
274
278
  const result = results[i];
275
279
  if (!result) continue;
276
280
  lines.push(`${i + 1}. **${result.title}**`);
277
- if (baseUrl) {
278
- const fullUrl = `${baseUrl.replace(/\/$/, "")}${result.route}`;
279
- lines.push(` URL: ${fullUrl}`);
280
- }
281
- lines.push(` Route: ${result.route}`);
281
+ lines.push(` URL: ${result.url}`);
282
282
  if (result.matchingHeadings && result.matchingHeadings.length > 0) {
283
283
  lines.push(` Matching sections: ${result.matchingHeadings.join(", ")}`);
284
284
  }
285
285
  lines.push(` ${result.snippet}`);
286
286
  lines.push("");
287
287
  }
288
+ lines.push("Use docs_fetch with the URL to retrieve the full page content.");
288
289
  return lines.join("\n");
289
290
  }
290
-
291
- // src/mcp/tools/docs-fetch.ts
292
- function formatPageContent(doc, baseUrl) {
291
+ var docsFetchInputSchema = {
292
+ url: zod.z.string().url().describe(
293
+ 'The full URL of the page to fetch (e.g., "https://docs.example.com/docs/getting-started")'
294
+ )
295
+ };
296
+ var docsFetchTool = {
297
+ name: "docs_fetch",
298
+ description: "Fetch the complete content of a documentation page. Use this after searching to get the full markdown content of a specific page.",
299
+ inputSchema: docsFetchInputSchema
300
+ };
301
+ function formatPageContent(doc) {
293
302
  if (!doc) {
294
- return "Page not found. Please check the route path and try again.";
303
+ return "Page not found. Please check the URL and try again.";
295
304
  }
296
305
  const lines = [];
297
306
  lines.push(`# ${doc.title}`);
@@ -300,12 +309,6 @@ function formatPageContent(doc, baseUrl) {
300
309
  lines.push(`> ${doc.description}`);
301
310
  lines.push("");
302
311
  }
303
- if (baseUrl) {
304
- const fullUrl = `${baseUrl.replace(/\/$/, "")}${doc.route}`;
305
- lines.push(`**URL:** ${fullUrl}`);
306
- }
307
- lines.push(`**Route:** ${doc.route}`);
308
- lines.push("");
309
312
  if (doc.headings.length > 0) {
310
313
  lines.push("## Contents");
311
314
  lines.push("");
@@ -351,17 +354,14 @@ var McpDocsServer = class {
351
354
  this.registerTools();
352
355
  }
353
356
  /**
354
- * Register all MCP tools using the SDK's registerTool API
357
+ * Register all MCP tools using definitions from tool files
355
358
  */
356
359
  registerTools() {
357
360
  this.mcpServer.registerTool(
358
- "docs_search",
361
+ docsSearchTool.name,
359
362
  {
360
- description: "Search the documentation for relevant pages. Returns matching documents with snippets and relevance scores. Use this to find information across all documentation.",
361
- inputSchema: {
362
- query: zod.z.string().min(1).describe("The search query string"),
363
- limit: zod.z.number().int().min(1).max(20).optional().default(5).describe("Maximum number of results to return (1-20, default: 5)")
364
- }
363
+ description: docsSearchTool.description,
364
+ inputSchema: docsSearchTool.inputSchema
365
365
  },
366
366
  async ({ query, limit }) => {
367
367
  await this.initialize();
@@ -374,9 +374,7 @@ var McpDocsServer = class {
374
374
  try {
375
375
  const results = await this.searchProvider.search(query, { limit });
376
376
  return {
377
- content: [
378
- { type: "text", text: formatSearchResults(results, this.config.baseUrl) }
379
- ]
377
+ content: [{ type: "text", text: formatSearchResults(results) }]
380
378
  };
381
379
  } catch (error) {
382
380
  console.error("[MCP] Search error:", error);
@@ -388,14 +386,12 @@ var McpDocsServer = class {
388
386
  }
389
387
  );
390
388
  this.mcpServer.registerTool(
391
- "docs_fetch",
389
+ docsFetchTool.name,
392
390
  {
393
- description: "Fetch the complete content of a documentation page. Use this when you need the full content of a specific page.",
394
- inputSchema: {
395
- route: zod.z.string().min(1).describe('The page route path (e.g., "/docs/getting-started" or "/api/reference")')
396
- }
391
+ description: docsFetchTool.description,
392
+ inputSchema: docsFetchTool.inputSchema
397
393
  },
398
- async ({ route }) => {
394
+ async ({ url }) => {
399
395
  await this.initialize();
400
396
  if (!this.searchProvider || !this.searchProvider.isReady()) {
401
397
  return {
@@ -404,14 +400,14 @@ var McpDocsServer = class {
404
400
  };
405
401
  }
406
402
  try {
407
- const doc = await this.getDocument(route);
403
+ const doc = await this.getDocument(url);
408
404
  return {
409
- content: [{ type: "text", text: formatPageContent(doc, this.config.baseUrl) }]
405
+ content: [{ type: "text", text: formatPageContent(doc) }]
410
406
  };
411
407
  } catch (error) {
412
- console.error("[MCP] Get page error:", error);
408
+ console.error("[MCP] Fetch error:", error);
413
409
  return {
414
- content: [{ type: "text", text: `Error getting page: ${String(error)}` }],
410
+ content: [{ type: "text", text: `Error fetching page: ${String(error)}` }],
415
411
  isError: true
416
412
  };
417
413
  }
@@ -419,24 +415,14 @@ var McpDocsServer = class {
419
415
  );
420
416
  }
421
417
  /**
422
- * Get a document by route using the search provider
418
+ * Get a document by URL using the search provider
423
419
  */
424
- async getDocument(route) {
420
+ async getDocument(url) {
425
421
  if (!this.searchProvider) {
426
422
  return null;
427
423
  }
428
424
  if (this.searchProvider.getDocument) {
429
- return this.searchProvider.getDocument(route);
430
- }
431
- if (this.searchProvider instanceof FlexSearchProvider) {
432
- const docs = this.searchProvider.getDocs();
433
- if (!docs) return null;
434
- if (docs[route]) {
435
- return docs[route];
436
- }
437
- const normalizedRoute = route.startsWith("/") ? route : `/${route}`;
438
- const withoutSlash = route.startsWith("/") ? route.slice(1) : route;
439
- return docs[normalizedRoute] ?? docs[withoutSlash] ?? null;
425
+ return this.searchProvider.getDocument(url);
440
426
  }
441
427
  return null;
442
428
  }
@@ -555,6 +541,16 @@ var McpDocsServer = class {
555
541
  }
556
542
  };
557
543
 
544
+ // src/adapters/cors.ts
545
+ var CORS_HEADERS = {
546
+ "Access-Control-Allow-Origin": "*",
547
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
548
+ "Access-Control-Allow-Headers": "Content-Type"
549
+ };
550
+ function getCorsHeaders() {
551
+ return { ...CORS_HEADERS };
552
+ }
553
+
558
554
  // src/adapters/vercel.ts
559
555
  function createVercelHandler(config) {
560
556
  let server = null;
@@ -565,12 +561,24 @@ function createVercelHandler(config) {
565
561
  return server;
566
562
  }
567
563
  return async function handler(req, res) {
564
+ const corsHeaders = getCorsHeaders();
565
+ if (req.method === "OPTIONS") {
566
+ res.writeHead(204, corsHeaders);
567
+ res.end();
568
+ return;
569
+ }
568
570
  if (req.method === "GET") {
571
+ Object.entries(corsHeaders).forEach(([key, value]) => {
572
+ res.setHeader(key, value);
573
+ });
569
574
  const mcpServer = getServer();
570
575
  const status = await mcpServer.getStatus();
571
576
  return res.status(200).json(status);
572
577
  }
573
578
  if (req.method !== "POST") {
579
+ Object.entries(corsHeaders).forEach(([key, value]) => {
580
+ res.setHeader(key, value);
581
+ });
574
582
  return res.status(405).json({
575
583
  jsonrpc: "2.0",
576
584
  id: null,
@@ -581,6 +589,9 @@ function createVercelHandler(config) {
581
589
  });
582
590
  }
583
591
  try {
592
+ Object.entries(corsHeaders).forEach(([key, value]) => {
593
+ res.setHeader(key, value);
594
+ });
584
595
  const mcpServer = getServer();
585
596
  await mcpServer.handleHttpRequest(req, res, req.body);
586
597
  } catch (error) {
@@ -617,8 +628,8 @@ function eventToRequest(event) {
617
628
  body: event.httpMethod !== "GET" && event.httpMethod !== "HEAD" ? body : void 0
618
629
  });
619
630
  }
620
- async function responseToNetlify(response) {
621
- const headers = {};
631
+ async function responseToNetlify(response, additionalHeaders) {
632
+ const headers = { ...additionalHeaders };
622
633
  response.headers.forEach((value, key) => {
623
634
  headers[key] = value;
624
635
  });
@@ -638,9 +649,17 @@ function createNetlifyHandler(config) {
638
649
  return server;
639
650
  }
640
651
  return async function handler(event, _context) {
652
+ const corsHeaders = getCorsHeaders();
641
653
  const headers = {
642
- "Content-Type": "application/json"
654
+ "Content-Type": "application/json",
655
+ ...corsHeaders
643
656
  };
657
+ if (event.httpMethod === "OPTIONS") {
658
+ return {
659
+ statusCode: 204,
660
+ headers: corsHeaders
661
+ };
662
+ }
644
663
  if (event.httpMethod === "GET") {
645
664
  const mcpServer = getServer();
646
665
  const status = await mcpServer.getStatus();
@@ -668,7 +687,7 @@ function createNetlifyHandler(config) {
668
687
  const mcpServer = getServer();
669
688
  const request = eventToRequest(event);
670
689
  const response = await mcpServer.handleWebRequest(request);
671
- return await responseToNetlify(response);
690
+ return await responseToNetlify(response, corsHeaders);
672
691
  } catch (error) {
673
692
  const errorMessage = error instanceof Error ? error.message : String(error);
674
693
  console.error("MCP Server Error:", error);
@@ -705,20 +724,16 @@ function createCloudflareHandler(config) {
705
724
  return server;
706
725
  }
707
726
  return async function fetch(request) {
708
- const headers = {
709
- "Access-Control-Allow-Origin": "*",
710
- "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
711
- "Access-Control-Allow-Headers": "Content-Type"
712
- };
727
+ const corsHeaders = getCorsHeaders();
713
728
  if (request.method === "OPTIONS") {
714
- return new Response(null, { status: 204, headers });
729
+ return new Response(null, { status: 204, headers: corsHeaders });
715
730
  }
716
731
  if (request.method === "GET") {
717
732
  const mcpServer = getServer();
718
733
  const status = await mcpServer.getStatus();
719
734
  return new Response(JSON.stringify(status), {
720
735
  status: 200,
721
- headers: { ...headers, "Content-Type": "application/json" }
736
+ headers: { ...corsHeaders, "Content-Type": "application/json" }
722
737
  });
723
738
  }
724
739
  if (request.method !== "POST") {
@@ -733,7 +748,7 @@ function createCloudflareHandler(config) {
733
748
  }),
734
749
  {
735
750
  status: 405,
736
- headers: { ...headers, "Content-Type": "application/json" }
751
+ headers: { ...corsHeaders, "Content-Type": "application/json" }
737
752
  }
738
753
  );
739
754
  }
@@ -741,7 +756,7 @@ function createCloudflareHandler(config) {
741
756
  const mcpServer = getServer();
742
757
  const response = await mcpServer.handleWebRequest(request);
743
758
  const newHeaders = new Headers(response.headers);
744
- Object.entries(headers).forEach(([key, value]) => {
759
+ Object.entries(corsHeaders).forEach(([key, value]) => {
745
760
  newHeaders.set(key, value);
746
761
  });
747
762
  return new Response(response.body, {
@@ -763,7 +778,7 @@ function createCloudflareHandler(config) {
763
778
  }),
764
779
  {
765
780
  status: 500,
766
- headers: { ...headers, "Content-Type": "application/json" }
781
+ headers: { ...corsHeaders, "Content-Type": "application/json" }
767
782
  }
768
783
  );
769
784
  }
@@ -927,9 +942,102 @@ compatibility_date = "2024-01-01"
927
942
  }
928
943
  ];
929
944
  }
945
+ function createNodeHandler(options) {
946
+ const { corsOrigin = "*", ...config } = options;
947
+ let server = null;
948
+ function getServer() {
949
+ if (!server) {
950
+ server = new McpDocsServer(config);
951
+ }
952
+ return server;
953
+ }
954
+ function setCorsHeaders(res) {
955
+ if (corsOrigin !== false) {
956
+ res.setHeader("Access-Control-Allow-Origin", corsOrigin);
957
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
958
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
959
+ }
960
+ }
961
+ return async function handler(req, res) {
962
+ setCorsHeaders(res);
963
+ if (req.method === "OPTIONS") {
964
+ res.writeHead(204);
965
+ res.end();
966
+ return;
967
+ }
968
+ if (req.method === "GET") {
969
+ try {
970
+ const mcpServer = getServer();
971
+ const status = await mcpServer.getStatus();
972
+ res.writeHead(200, { "Content-Type": "application/json" });
973
+ res.end(JSON.stringify(status, null, 2));
974
+ } catch (error) {
975
+ const message = error instanceof Error ? error.message : String(error);
976
+ res.writeHead(500, { "Content-Type": "application/json" });
977
+ res.end(JSON.stringify({ error: message }));
978
+ }
979
+ return;
980
+ }
981
+ if (req.method !== "POST") {
982
+ res.writeHead(405, { "Content-Type": "application/json" });
983
+ res.end(
984
+ JSON.stringify({
985
+ jsonrpc: "2.0",
986
+ id: null,
987
+ error: {
988
+ code: -32600,
989
+ message: "Method not allowed. Use POST for MCP requests, GET for status."
990
+ }
991
+ })
992
+ );
993
+ return;
994
+ }
995
+ try {
996
+ const body = await parseRequestBody(req);
997
+ const mcpServer = getServer();
998
+ await mcpServer.handleHttpRequest(req, res, body);
999
+ } catch (error) {
1000
+ const message = error instanceof Error ? error.message : String(error);
1001
+ console.error("[MCP] Request error:", error);
1002
+ res.writeHead(500, { "Content-Type": "application/json" });
1003
+ res.end(
1004
+ JSON.stringify({
1005
+ jsonrpc: "2.0",
1006
+ id: null,
1007
+ error: {
1008
+ code: -32603,
1009
+ message: `Internal server error: ${message}`
1010
+ }
1011
+ })
1012
+ );
1013
+ }
1014
+ };
1015
+ }
1016
+ function createNodeServer(options) {
1017
+ const handler = createNodeHandler(options);
1018
+ return http.createServer(handler);
1019
+ }
1020
+ async function parseRequestBody(req) {
1021
+ return new Promise((resolve, reject) => {
1022
+ let body = "";
1023
+ req.on("data", (chunk) => {
1024
+ body += chunk;
1025
+ });
1026
+ req.on("end", () => {
1027
+ try {
1028
+ resolve(body ? JSON.parse(body) : void 0);
1029
+ } catch {
1030
+ reject(new Error("Invalid JSON in request body"));
1031
+ }
1032
+ });
1033
+ req.on("error", reject);
1034
+ });
1035
+ }
930
1036
 
931
1037
  exports.createCloudflareHandler = createCloudflareHandler;
932
1038
  exports.createNetlifyHandler = createNetlifyHandler;
1039
+ exports.createNodeHandler = createNodeHandler;
1040
+ exports.createNodeServer = createNodeServer;
933
1041
  exports.createVercelHandler = createVercelHandler;
934
1042
  exports.generateAdapterFiles = generateAdapterFiles;
935
1043
  //# sourceMappingURL=adapters-entry.js.map