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.
@@ -1,10 +1,10 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
3
3
  import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
4
- import { z } from 'zod';
5
4
  import FlexSearch from 'flexsearch';
6
5
  import fs from 'fs-extra';
7
- import 'http';
6
+ import { z } from 'zod';
7
+ import { createServer } from 'http';
8
8
 
9
9
  // src/mcp/server.ts
10
10
  var FIELD_WEIGHTS = {
@@ -74,6 +74,8 @@ function searchIndex(index, docs, query, options = {}) {
74
74
  const doc = docs[docId];
75
75
  if (!doc) continue;
76
76
  results.push({
77
+ url: docId,
78
+ // docId is the full URL when indexed with baseUrl
77
79
  route: doc.route,
78
80
  title: doc.title,
79
81
  score,
@@ -181,16 +183,11 @@ var FlexSearchProvider = class {
181
183
  const limit = options?.limit ?? 5;
182
184
  return searchIndex(this.searchIndex, this.docs, query, { limit });
183
185
  }
184
- async getDocument(route) {
186
+ async getDocument(url) {
185
187
  if (!this.docs) {
186
188
  throw new Error("[FlexSearch] Provider not initialized");
187
189
  }
188
- if (this.docs[route]) {
189
- return this.docs[route];
190
- }
191
- const normalizedRoute = route.startsWith("/") ? route : `/${route}`;
192
- const withoutSlash = route.startsWith("/") ? route.slice(1) : route;
193
- return this.docs[normalizedRoute] ?? this.docs[withoutSlash] ?? null;
190
+ return this.docs[url] ?? null;
194
191
  }
195
192
  async healthCheck() {
196
193
  if (!this.isReady()) {
@@ -255,9 +252,16 @@ function isSearchProvider(obj) {
255
252
  const provider = obj;
256
253
  return typeof provider.name === "string" && typeof provider.initialize === "function" && typeof provider.isReady === "function" && typeof provider.search === "function";
257
254
  }
258
-
259
- // src/mcp/tools/docs-search.ts
260
- function formatSearchResults(results, baseUrl) {
255
+ var docsSearchInputSchema = {
256
+ query: z.string().min(1).describe("The search query string"),
257
+ limit: z.number().int().min(1).max(20).optional().default(5).describe("Maximum number of results to return (1-20, default: 5)")
258
+ };
259
+ var docsSearchTool = {
260
+ name: "docs_search",
261
+ description: "Search the documentation for relevant pages. Returns matching documents with URLs, snippets, and relevance scores. Use this to find information across all documentation.",
262
+ inputSchema: docsSearchInputSchema
263
+ };
264
+ function formatSearchResults(results) {
261
265
  if (results.length === 0) {
262
266
  return "No matching documents found.";
263
267
  }
@@ -267,24 +271,29 @@ function formatSearchResults(results, baseUrl) {
267
271
  const result = results[i];
268
272
  if (!result) continue;
269
273
  lines.push(`${i + 1}. **${result.title}**`);
270
- if (baseUrl) {
271
- const fullUrl = `${baseUrl.replace(/\/$/, "")}${result.route}`;
272
- lines.push(` URL: ${fullUrl}`);
273
- }
274
- lines.push(` Route: ${result.route}`);
274
+ lines.push(` URL: ${result.url}`);
275
275
  if (result.matchingHeadings && result.matchingHeadings.length > 0) {
276
276
  lines.push(` Matching sections: ${result.matchingHeadings.join(", ")}`);
277
277
  }
278
278
  lines.push(` ${result.snippet}`);
279
279
  lines.push("");
280
280
  }
281
+ lines.push("Use docs_fetch with the URL to retrieve the full page content.");
281
282
  return lines.join("\n");
282
283
  }
283
-
284
- // src/mcp/tools/docs-fetch.ts
285
- function formatPageContent(doc, baseUrl) {
284
+ var docsFetchInputSchema = {
285
+ url: z.string().url().describe(
286
+ 'The full URL of the page to fetch (e.g., "https://docs.example.com/docs/getting-started")'
287
+ )
288
+ };
289
+ var docsFetchTool = {
290
+ name: "docs_fetch",
291
+ description: "Fetch the complete content of a documentation page. Use this after searching to get the full markdown content of a specific page.",
292
+ inputSchema: docsFetchInputSchema
293
+ };
294
+ function formatPageContent(doc) {
286
295
  if (!doc) {
287
- return "Page not found. Please check the route path and try again.";
296
+ return "Page not found. Please check the URL and try again.";
288
297
  }
289
298
  const lines = [];
290
299
  lines.push(`# ${doc.title}`);
@@ -293,12 +302,6 @@ function formatPageContent(doc, baseUrl) {
293
302
  lines.push(`> ${doc.description}`);
294
303
  lines.push("");
295
304
  }
296
- if (baseUrl) {
297
- const fullUrl = `${baseUrl.replace(/\/$/, "")}${doc.route}`;
298
- lines.push(`**URL:** ${fullUrl}`);
299
- }
300
- lines.push(`**Route:** ${doc.route}`);
301
- lines.push("");
302
305
  if (doc.headings.length > 0) {
303
306
  lines.push("## Contents");
304
307
  lines.push("");
@@ -344,17 +347,14 @@ var McpDocsServer = class {
344
347
  this.registerTools();
345
348
  }
346
349
  /**
347
- * Register all MCP tools using the SDK's registerTool API
350
+ * Register all MCP tools using definitions from tool files
348
351
  */
349
352
  registerTools() {
350
353
  this.mcpServer.registerTool(
351
- "docs_search",
354
+ docsSearchTool.name,
352
355
  {
353
- description: "Search the documentation for relevant pages. Returns matching documents with snippets and relevance scores. Use this to find information across all documentation.",
354
- inputSchema: {
355
- query: z.string().min(1).describe("The search query string"),
356
- limit: z.number().int().min(1).max(20).optional().default(5).describe("Maximum number of results to return (1-20, default: 5)")
357
- }
356
+ description: docsSearchTool.description,
357
+ inputSchema: docsSearchTool.inputSchema
358
358
  },
359
359
  async ({ query, limit }) => {
360
360
  await this.initialize();
@@ -367,9 +367,7 @@ var McpDocsServer = class {
367
367
  try {
368
368
  const results = await this.searchProvider.search(query, { limit });
369
369
  return {
370
- content: [
371
- { type: "text", text: formatSearchResults(results, this.config.baseUrl) }
372
- ]
370
+ content: [{ type: "text", text: formatSearchResults(results) }]
373
371
  };
374
372
  } catch (error) {
375
373
  console.error("[MCP] Search error:", error);
@@ -381,14 +379,12 @@ var McpDocsServer = class {
381
379
  }
382
380
  );
383
381
  this.mcpServer.registerTool(
384
- "docs_fetch",
382
+ docsFetchTool.name,
385
383
  {
386
- description: "Fetch the complete content of a documentation page. Use this when you need the full content of a specific page.",
387
- inputSchema: {
388
- route: z.string().min(1).describe('The page route path (e.g., "/docs/getting-started" or "/api/reference")')
389
- }
384
+ description: docsFetchTool.description,
385
+ inputSchema: docsFetchTool.inputSchema
390
386
  },
391
- async ({ route }) => {
387
+ async ({ url }) => {
392
388
  await this.initialize();
393
389
  if (!this.searchProvider || !this.searchProvider.isReady()) {
394
390
  return {
@@ -397,14 +393,14 @@ var McpDocsServer = class {
397
393
  };
398
394
  }
399
395
  try {
400
- const doc = await this.getDocument(route);
396
+ const doc = await this.getDocument(url);
401
397
  return {
402
- content: [{ type: "text", text: formatPageContent(doc, this.config.baseUrl) }]
398
+ content: [{ type: "text", text: formatPageContent(doc) }]
403
399
  };
404
400
  } catch (error) {
405
- console.error("[MCP] Get page error:", error);
401
+ console.error("[MCP] Fetch error:", error);
406
402
  return {
407
- content: [{ type: "text", text: `Error getting page: ${String(error)}` }],
403
+ content: [{ type: "text", text: `Error fetching page: ${String(error)}` }],
408
404
  isError: true
409
405
  };
410
406
  }
@@ -412,24 +408,14 @@ var McpDocsServer = class {
412
408
  );
413
409
  }
414
410
  /**
415
- * Get a document by route using the search provider
411
+ * Get a document by URL using the search provider
416
412
  */
417
- async getDocument(route) {
413
+ async getDocument(url) {
418
414
  if (!this.searchProvider) {
419
415
  return null;
420
416
  }
421
417
  if (this.searchProvider.getDocument) {
422
- return this.searchProvider.getDocument(route);
423
- }
424
- if (this.searchProvider instanceof FlexSearchProvider) {
425
- const docs = this.searchProvider.getDocs();
426
- if (!docs) return null;
427
- if (docs[route]) {
428
- return docs[route];
429
- }
430
- const normalizedRoute = route.startsWith("/") ? route : `/${route}`;
431
- const withoutSlash = route.startsWith("/") ? route.slice(1) : route;
432
- return docs[normalizedRoute] ?? docs[withoutSlash] ?? null;
418
+ return this.searchProvider.getDocument(url);
433
419
  }
434
420
  return null;
435
421
  }
@@ -548,6 +534,16 @@ var McpDocsServer = class {
548
534
  }
549
535
  };
550
536
 
537
+ // src/adapters/cors.ts
538
+ var CORS_HEADERS = {
539
+ "Access-Control-Allow-Origin": "*",
540
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
541
+ "Access-Control-Allow-Headers": "Content-Type"
542
+ };
543
+ function getCorsHeaders() {
544
+ return { ...CORS_HEADERS };
545
+ }
546
+
551
547
  // src/adapters/vercel.ts
552
548
  function createVercelHandler(config) {
553
549
  let server = null;
@@ -558,12 +554,24 @@ function createVercelHandler(config) {
558
554
  return server;
559
555
  }
560
556
  return async function handler(req, res) {
557
+ const corsHeaders = getCorsHeaders();
558
+ if (req.method === "OPTIONS") {
559
+ res.writeHead(204, corsHeaders);
560
+ res.end();
561
+ return;
562
+ }
561
563
  if (req.method === "GET") {
564
+ Object.entries(corsHeaders).forEach(([key, value]) => {
565
+ res.setHeader(key, value);
566
+ });
562
567
  const mcpServer = getServer();
563
568
  const status = await mcpServer.getStatus();
564
569
  return res.status(200).json(status);
565
570
  }
566
571
  if (req.method !== "POST") {
572
+ Object.entries(corsHeaders).forEach(([key, value]) => {
573
+ res.setHeader(key, value);
574
+ });
567
575
  return res.status(405).json({
568
576
  jsonrpc: "2.0",
569
577
  id: null,
@@ -574,6 +582,9 @@ function createVercelHandler(config) {
574
582
  });
575
583
  }
576
584
  try {
585
+ Object.entries(corsHeaders).forEach(([key, value]) => {
586
+ res.setHeader(key, value);
587
+ });
577
588
  const mcpServer = getServer();
578
589
  await mcpServer.handleHttpRequest(req, res, req.body);
579
590
  } catch (error) {
@@ -610,8 +621,8 @@ function eventToRequest(event) {
610
621
  body: event.httpMethod !== "GET" && event.httpMethod !== "HEAD" ? body : void 0
611
622
  });
612
623
  }
613
- async function responseToNetlify(response) {
614
- const headers = {};
624
+ async function responseToNetlify(response, additionalHeaders) {
625
+ const headers = { ...additionalHeaders };
615
626
  response.headers.forEach((value, key) => {
616
627
  headers[key] = value;
617
628
  });
@@ -631,9 +642,17 @@ function createNetlifyHandler(config) {
631
642
  return server;
632
643
  }
633
644
  return async function handler(event, _context) {
645
+ const corsHeaders = getCorsHeaders();
634
646
  const headers = {
635
- "Content-Type": "application/json"
647
+ "Content-Type": "application/json",
648
+ ...corsHeaders
636
649
  };
650
+ if (event.httpMethod === "OPTIONS") {
651
+ return {
652
+ statusCode: 204,
653
+ headers: corsHeaders
654
+ };
655
+ }
637
656
  if (event.httpMethod === "GET") {
638
657
  const mcpServer = getServer();
639
658
  const status = await mcpServer.getStatus();
@@ -661,7 +680,7 @@ function createNetlifyHandler(config) {
661
680
  const mcpServer = getServer();
662
681
  const request = eventToRequest(event);
663
682
  const response = await mcpServer.handleWebRequest(request);
664
- return await responseToNetlify(response);
683
+ return await responseToNetlify(response, corsHeaders);
665
684
  } catch (error) {
666
685
  const errorMessage = error instanceof Error ? error.message : String(error);
667
686
  console.error("MCP Server Error:", error);
@@ -698,20 +717,16 @@ function createCloudflareHandler(config) {
698
717
  return server;
699
718
  }
700
719
  return async function fetch(request) {
701
- const headers = {
702
- "Access-Control-Allow-Origin": "*",
703
- "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
704
- "Access-Control-Allow-Headers": "Content-Type"
705
- };
720
+ const corsHeaders = getCorsHeaders();
706
721
  if (request.method === "OPTIONS") {
707
- return new Response(null, { status: 204, headers });
722
+ return new Response(null, { status: 204, headers: corsHeaders });
708
723
  }
709
724
  if (request.method === "GET") {
710
725
  const mcpServer = getServer();
711
726
  const status = await mcpServer.getStatus();
712
727
  return new Response(JSON.stringify(status), {
713
728
  status: 200,
714
- headers: { ...headers, "Content-Type": "application/json" }
729
+ headers: { ...corsHeaders, "Content-Type": "application/json" }
715
730
  });
716
731
  }
717
732
  if (request.method !== "POST") {
@@ -726,7 +741,7 @@ function createCloudflareHandler(config) {
726
741
  }),
727
742
  {
728
743
  status: 405,
729
- headers: { ...headers, "Content-Type": "application/json" }
744
+ headers: { ...corsHeaders, "Content-Type": "application/json" }
730
745
  }
731
746
  );
732
747
  }
@@ -734,7 +749,7 @@ function createCloudflareHandler(config) {
734
749
  const mcpServer = getServer();
735
750
  const response = await mcpServer.handleWebRequest(request);
736
751
  const newHeaders = new Headers(response.headers);
737
- Object.entries(headers).forEach(([key, value]) => {
752
+ Object.entries(corsHeaders).forEach(([key, value]) => {
738
753
  newHeaders.set(key, value);
739
754
  });
740
755
  return new Response(response.body, {
@@ -756,7 +771,7 @@ function createCloudflareHandler(config) {
756
771
  }),
757
772
  {
758
773
  status: 500,
759
- headers: { ...headers, "Content-Type": "application/json" }
774
+ headers: { ...corsHeaders, "Content-Type": "application/json" }
760
775
  }
761
776
  );
762
777
  }
@@ -920,7 +935,98 @@ compatibility_date = "2024-01-01"
920
935
  }
921
936
  ];
922
937
  }
938
+ function createNodeHandler(options) {
939
+ const { corsOrigin = "*", ...config } = options;
940
+ let server = null;
941
+ function getServer() {
942
+ if (!server) {
943
+ server = new McpDocsServer(config);
944
+ }
945
+ return server;
946
+ }
947
+ function setCorsHeaders(res) {
948
+ if (corsOrigin !== false) {
949
+ res.setHeader("Access-Control-Allow-Origin", corsOrigin);
950
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
951
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
952
+ }
953
+ }
954
+ return async function handler(req, res) {
955
+ setCorsHeaders(res);
956
+ if (req.method === "OPTIONS") {
957
+ res.writeHead(204);
958
+ res.end();
959
+ return;
960
+ }
961
+ if (req.method === "GET") {
962
+ try {
963
+ const mcpServer = getServer();
964
+ const status = await mcpServer.getStatus();
965
+ res.writeHead(200, { "Content-Type": "application/json" });
966
+ res.end(JSON.stringify(status, null, 2));
967
+ } catch (error) {
968
+ const message = error instanceof Error ? error.message : String(error);
969
+ res.writeHead(500, { "Content-Type": "application/json" });
970
+ res.end(JSON.stringify({ error: message }));
971
+ }
972
+ return;
973
+ }
974
+ if (req.method !== "POST") {
975
+ res.writeHead(405, { "Content-Type": "application/json" });
976
+ res.end(
977
+ JSON.stringify({
978
+ jsonrpc: "2.0",
979
+ id: null,
980
+ error: {
981
+ code: -32600,
982
+ message: "Method not allowed. Use POST for MCP requests, GET for status."
983
+ }
984
+ })
985
+ );
986
+ return;
987
+ }
988
+ try {
989
+ const body = await parseRequestBody(req);
990
+ const mcpServer = getServer();
991
+ await mcpServer.handleHttpRequest(req, res, body);
992
+ } catch (error) {
993
+ const message = error instanceof Error ? error.message : String(error);
994
+ console.error("[MCP] Request error:", error);
995
+ res.writeHead(500, { "Content-Type": "application/json" });
996
+ res.end(
997
+ JSON.stringify({
998
+ jsonrpc: "2.0",
999
+ id: null,
1000
+ error: {
1001
+ code: -32603,
1002
+ message: `Internal server error: ${message}`
1003
+ }
1004
+ })
1005
+ );
1006
+ }
1007
+ };
1008
+ }
1009
+ function createNodeServer(options) {
1010
+ const handler = createNodeHandler(options);
1011
+ return createServer(handler);
1012
+ }
1013
+ async function parseRequestBody(req) {
1014
+ return new Promise((resolve, reject) => {
1015
+ let body = "";
1016
+ req.on("data", (chunk) => {
1017
+ body += chunk;
1018
+ });
1019
+ req.on("end", () => {
1020
+ try {
1021
+ resolve(body ? JSON.parse(body) : void 0);
1022
+ } catch {
1023
+ reject(new Error("Invalid JSON in request body"));
1024
+ }
1025
+ });
1026
+ req.on("error", reject);
1027
+ });
1028
+ }
923
1029
 
924
- export { createCloudflareHandler, createNetlifyHandler, createVercelHandler, generateAdapterFiles };
1030
+ export { createCloudflareHandler, createNetlifyHandler, createNodeHandler, createNodeServer, createVercelHandler, generateAdapterFiles };
925
1031
  //# sourceMappingURL=adapters-entry.mjs.map
926
1032
  //# sourceMappingURL=adapters-entry.mjs.map