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.
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ var unified = require('unified');
9
9
  var rehypeParse = require('rehype-parse');
10
10
  var hastUtilSelect = require('hast-util-select');
11
11
  var hastUtilToString = require('hast-util-to-string');
12
+ var hastUtilToHtml = require('hast-util-to-html');
12
13
  var rehypeRemark = require('rehype-remark');
13
14
  var remarkStringify = require('remark-stringify');
14
15
  var remarkGfm = require('remark-gfm');
@@ -197,7 +198,7 @@ async function extractContent(filePath, options) {
197
198
  let contentHtml = "";
198
199
  if (contentElement) {
199
200
  const cleanedElement = cleanContentElement(contentElement, options.excludeSelectors);
200
- contentHtml = serializeElement(cleanedElement);
201
+ contentHtml = hastUtilToHtml.toHtml(cleanedElement);
201
202
  }
202
203
  return {
203
204
  title,
@@ -205,57 +206,6 @@ async function extractContent(filePath, options) {
205
206
  contentHtml
206
207
  };
207
208
  }
208
- function serializeElement(element) {
209
- const voidElements = /* @__PURE__ */ new Set([
210
- "area",
211
- "base",
212
- "br",
213
- "col",
214
- "embed",
215
- "hr",
216
- "img",
217
- "input",
218
- "link",
219
- "meta",
220
- "param",
221
- "source",
222
- "track",
223
- "wbr"
224
- ]);
225
- function serialize(node) {
226
- if (!node || typeof node !== "object") return "";
227
- const n = node;
228
- if (n.type === "text") {
229
- return n.value ?? "";
230
- }
231
- if (n.type === "element" && n.tagName) {
232
- const tagName = n.tagName;
233
- const props = n.properties ?? {};
234
- const attrs = [];
235
- for (const [key, value] of Object.entries(props)) {
236
- if (key === "className" && Array.isArray(value)) {
237
- attrs.push(`class="${value.join(" ")}"`);
238
- } else if (typeof value === "boolean") {
239
- if (value) attrs.push(key);
240
- } else if (value !== void 0 && value !== null) {
241
- attrs.push(`${key}="${String(value)}"`);
242
- }
243
- }
244
- const attrStr = attrs.length > 0 ? " " + attrs.join(" ") : "";
245
- if (voidElements.has(tagName)) {
246
- return `<${tagName}${attrStr} />`;
247
- }
248
- const children = n.children ?? [];
249
- const childrenHtml = children.map(serialize).join("");
250
- return `<${tagName}${attrStr}>${childrenHtml}</${tagName}>`;
251
- }
252
- if (n.type === "root" && n.children) {
253
- return n.children.map(serialize).join("");
254
- }
255
- return "";
256
- }
257
- return serialize(element);
258
- }
259
209
  async function htmlToMarkdown(html) {
260
210
  if (!html || html.trim().length === 0) {
261
211
  return "";
@@ -392,9 +342,10 @@ function createSearchIndex() {
392
342
  }
393
343
  });
394
344
  }
395
- function addDocumentToIndex(index, doc) {
345
+ function addDocumentToIndex(index, doc, baseUrl) {
346
+ const id = baseUrl ? `${baseUrl.replace(/\/$/, "")}${doc.route}` : doc.route;
396
347
  const indexable = {
397
- id: doc.route,
348
+ id,
398
349
  title: doc.title,
399
350
  content: doc.markdown,
400
351
  headings: doc.headings.map((h) => h.text).join(" "),
@@ -402,10 +353,10 @@ function addDocumentToIndex(index, doc) {
402
353
  };
403
354
  index.add(indexable);
404
355
  }
405
- function buildSearchIndex(docs) {
356
+ function buildSearchIndex(docs, baseUrl) {
406
357
  const index = createSearchIndex();
407
358
  for (const doc of docs) {
408
- addDocumentToIndex(index, doc);
359
+ addDocumentToIndex(index, doc, baseUrl);
409
360
  }
410
361
  return index;
411
362
  }
@@ -436,6 +387,8 @@ function searchIndex(index, docs, query, options = {}) {
436
387
  const doc = docs[docId];
437
388
  if (!doc) continue;
438
389
  results.push({
390
+ url: docId,
391
+ // docId is the full URL when indexed with baseUrl
439
392
  route: doc.route,
440
393
  title: doc.title,
441
394
  score,
@@ -510,6 +463,7 @@ async function importSearchIndex(data) {
510
463
  // src/providers/indexers/flexsearch-indexer.ts
511
464
  var FlexSearchIndexer = class {
512
465
  name = "flexsearch";
466
+ baseUrl = "";
513
467
  docsIndex = {};
514
468
  exportedIndex = null;
515
469
  docCount = 0;
@@ -520,7 +474,8 @@ var FlexSearchIndexer = class {
520
474
  shouldRun() {
521
475
  return true;
522
476
  }
523
- async initialize(_context) {
477
+ async initialize(context) {
478
+ this.baseUrl = context.baseUrl.replace(/\/$/, "");
524
479
  this.docsIndex = {};
525
480
  this.exportedIndex = null;
526
481
  this.docCount = 0;
@@ -528,10 +483,11 @@ var FlexSearchIndexer = class {
528
483
  async indexDocuments(docs) {
529
484
  this.docCount = docs.length;
530
485
  for (const doc of docs) {
531
- this.docsIndex[doc.route] = doc;
486
+ const fullUrl = `${this.baseUrl}${doc.route}`;
487
+ this.docsIndex[fullUrl] = doc;
532
488
  }
533
489
  console.log("[FlexSearch] Building search index...");
534
- const searchIndex2 = buildSearchIndex(docs);
490
+ const searchIndex2 = buildSearchIndex(docs, this.baseUrl);
535
491
  this.exportedIndex = await exportSearchIndex(searchIndex2);
536
492
  console.log(`[FlexSearch] Indexed ${this.docCount} documents`);
537
493
  }
@@ -591,16 +547,11 @@ var FlexSearchProvider = class {
591
547
  const limit = options?.limit ?? 5;
592
548
  return searchIndex(this.searchIndex, this.docs, query, { limit });
593
549
  }
594
- async getDocument(route) {
550
+ async getDocument(url) {
595
551
  if (!this.docs) {
596
552
  throw new Error("[FlexSearch] Provider not initialized");
597
553
  }
598
- if (this.docs[route]) {
599
- return this.docs[route];
600
- }
601
- const normalizedRoute = route.startsWith("/") ? route : `/${route}`;
602
- const withoutSlash = route.startsWith("/") ? route.slice(1) : route;
603
- return this.docs[normalizedRoute] ?? this.docs[withoutSlash] ?? null;
554
+ return this.docs[url] ?? null;
604
555
  }
605
556
  async healthCheck() {
606
557
  if (!this.isReady()) {
@@ -833,28 +784,16 @@ function mcpServerPlugin(context, options) {
833
784
  }
834
785
  };
835
786
  }
836
-
837
- // src/mcp/tools/docs-search.ts
787
+ var docsSearchInputSchema = {
788
+ query: zod.z.string().min(1).describe("The search query string"),
789
+ limit: zod.z.number().int().min(1).max(20).optional().default(5).describe("Maximum number of results to return (1-20, default: 5)")
790
+ };
838
791
  var docsSearchTool = {
839
792
  name: "docs_search",
840
- description: "Search across developer documentation. Returns ranked results with snippets and matching headings.",
841
- inputSchema: {
842
- type: "object",
843
- properties: {
844
- query: {
845
- type: "string",
846
- description: "Search query string"
847
- },
848
- limit: {
849
- type: "number",
850
- description: "Maximum number of results to return (default: 5, max: 20)",
851
- default: 5
852
- }
853
- },
854
- required: ["query"]
855
- }
793
+ description: "Search the documentation for relevant pages. Returns matching documents with URLs, snippets, and relevance scores. Use this to find information across all documentation.",
794
+ inputSchema: docsSearchInputSchema
856
795
  };
857
- function formatSearchResults(results, baseUrl) {
796
+ function formatSearchResults(results) {
858
797
  if (results.length === 0) {
859
798
  return "No matching documents found.";
860
799
  }
@@ -864,38 +803,29 @@ function formatSearchResults(results, baseUrl) {
864
803
  const result = results[i];
865
804
  if (!result) continue;
866
805
  lines.push(`${i + 1}. **${result.title}**`);
867
- if (baseUrl) {
868
- const fullUrl = `${baseUrl.replace(/\/$/, "")}${result.route}`;
869
- lines.push(` URL: ${fullUrl}`);
870
- }
871
- lines.push(` Route: ${result.route}`);
806
+ lines.push(` URL: ${result.url}`);
872
807
  if (result.matchingHeadings && result.matchingHeadings.length > 0) {
873
808
  lines.push(` Matching sections: ${result.matchingHeadings.join(", ")}`);
874
809
  }
875
810
  lines.push(` ${result.snippet}`);
876
811
  lines.push("");
877
812
  }
813
+ lines.push("Use docs_fetch with the URL to retrieve the full page content.");
878
814
  return lines.join("\n");
879
815
  }
880
-
881
- // src/mcp/tools/docs-fetch.ts
816
+ var docsFetchInputSchema = {
817
+ url: zod.z.string().url().describe(
818
+ 'The full URL of the page to fetch (e.g., "https://docs.example.com/docs/getting-started")'
819
+ )
820
+ };
882
821
  var docsFetchTool = {
883
822
  name: "docs_fetch",
884
- description: "Fetch the complete content of a documentation page. Use this after searching to get full page content.",
885
- inputSchema: {
886
- type: "object",
887
- properties: {
888
- route: {
889
- type: "string",
890
- description: "The route path of the page (e.g., /docs/getting-started)"
891
- }
892
- },
893
- required: ["route"]
894
- }
823
+ description: "Fetch the complete content of a documentation page. Use this after searching to get the full markdown content of a specific page.",
824
+ inputSchema: docsFetchInputSchema
895
825
  };
896
- function formatPageContent(doc, baseUrl) {
826
+ function formatPageContent(doc) {
897
827
  if (!doc) {
898
- return "Page not found. Please check the route path and try again.";
828
+ return "Page not found. Please check the URL and try again.";
899
829
  }
900
830
  const lines = [];
901
831
  lines.push(`# ${doc.title}`);
@@ -904,12 +834,6 @@ function formatPageContent(doc, baseUrl) {
904
834
  lines.push(`> ${doc.description}`);
905
835
  lines.push("");
906
836
  }
907
- if (baseUrl) {
908
- const fullUrl = `${baseUrl.replace(/\/$/, "")}${doc.route}`;
909
- lines.push(`**URL:** ${fullUrl}`);
910
- }
911
- lines.push(`**Route:** ${doc.route}`);
912
- lines.push("");
913
837
  if (doc.headings.length > 0) {
914
838
  lines.push("## Contents");
915
839
  lines.push("");
@@ -955,17 +879,14 @@ var McpDocsServer = class {
955
879
  this.registerTools();
956
880
  }
957
881
  /**
958
- * Register all MCP tools using the SDK's registerTool API
882
+ * Register all MCP tools using definitions from tool files
959
883
  */
960
884
  registerTools() {
961
885
  this.mcpServer.registerTool(
962
- "docs_search",
886
+ docsSearchTool.name,
963
887
  {
964
- description: "Search the documentation for relevant pages. Returns matching documents with snippets and relevance scores. Use this to find information across all documentation.",
965
- inputSchema: {
966
- query: zod.z.string().min(1).describe("The search query string"),
967
- limit: zod.z.number().int().min(1).max(20).optional().default(5).describe("Maximum number of results to return (1-20, default: 5)")
968
- }
888
+ description: docsSearchTool.description,
889
+ inputSchema: docsSearchTool.inputSchema
969
890
  },
970
891
  async ({ query, limit }) => {
971
892
  await this.initialize();
@@ -978,9 +899,7 @@ var McpDocsServer = class {
978
899
  try {
979
900
  const results = await this.searchProvider.search(query, { limit });
980
901
  return {
981
- content: [
982
- { type: "text", text: formatSearchResults(results, this.config.baseUrl) }
983
- ]
902
+ content: [{ type: "text", text: formatSearchResults(results) }]
984
903
  };
985
904
  } catch (error) {
986
905
  console.error("[MCP] Search error:", error);
@@ -992,14 +911,12 @@ var McpDocsServer = class {
992
911
  }
993
912
  );
994
913
  this.mcpServer.registerTool(
995
- "docs_fetch",
914
+ docsFetchTool.name,
996
915
  {
997
- description: "Fetch the complete content of a documentation page. Use this when you need the full content of a specific page.",
998
- inputSchema: {
999
- route: zod.z.string().min(1).describe('The page route path (e.g., "/docs/getting-started" or "/api/reference")')
1000
- }
916
+ description: docsFetchTool.description,
917
+ inputSchema: docsFetchTool.inputSchema
1001
918
  },
1002
- async ({ route }) => {
919
+ async ({ url }) => {
1003
920
  await this.initialize();
1004
921
  if (!this.searchProvider || !this.searchProvider.isReady()) {
1005
922
  return {
@@ -1008,14 +925,14 @@ var McpDocsServer = class {
1008
925
  };
1009
926
  }
1010
927
  try {
1011
- const doc = await this.getDocument(route);
928
+ const doc = await this.getDocument(url);
1012
929
  return {
1013
- content: [{ type: "text", text: formatPageContent(doc, this.config.baseUrl) }]
930
+ content: [{ type: "text", text: formatPageContent(doc) }]
1014
931
  };
1015
932
  } catch (error) {
1016
- console.error("[MCP] Get page error:", error);
933
+ console.error("[MCP] Fetch error:", error);
1017
934
  return {
1018
- content: [{ type: "text", text: `Error getting page: ${String(error)}` }],
935
+ content: [{ type: "text", text: `Error fetching page: ${String(error)}` }],
1019
936
  isError: true
1020
937
  };
1021
938
  }
@@ -1023,24 +940,14 @@ var McpDocsServer = class {
1023
940
  );
1024
941
  }
1025
942
  /**
1026
- * Get a document by route using the search provider
943
+ * Get a document by URL using the search provider
1027
944
  */
1028
- async getDocument(route) {
945
+ async getDocument(url) {
1029
946
  if (!this.searchProvider) {
1030
947
  return null;
1031
948
  }
1032
949
  if (this.searchProvider.getDocument) {
1033
- return this.searchProvider.getDocument(route);
1034
- }
1035
- if (this.searchProvider instanceof FlexSearchProvider) {
1036
- const docs = this.searchProvider.getDocs();
1037
- if (!docs) return null;
1038
- if (docs[route]) {
1039
- return docs[route];
1040
- }
1041
- const normalizedRoute = route.startsWith("/") ? route : `/${route}`;
1042
- const withoutSlash = route.startsWith("/") ? route.slice(1) : route;
1043
- return docs[normalizedRoute] ?? docs[withoutSlash] ?? null;
950
+ return this.searchProvider.getDocument(url);
1044
951
  }
1045
952
  return null;
1046
953
  }