docusaurus-plugin-mcp-server 0.6.0 → 0.8.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/adapters-entry.d.mts +1 -1
- package/dist/adapters-entry.d.ts +1 -1
- package/dist/adapters-entry.js +195 -177
- package/dist/adapters-entry.js.map +1 -1
- package/dist/adapters-entry.mjs +194 -176
- package/dist/adapters-entry.mjs.map +1 -1
- package/dist/cli/verify.js +192 -175
- package/dist/cli/verify.js.map +1 -1
- package/dist/cli/verify.mjs +192 -175
- package/dist/cli/verify.mjs.map +1 -1
- package/dist/{index-CzA4FjeE.d.mts → index-4g0ZZK3z.d.mts} +33 -0
- package/dist/{index-CzA4FjeE.d.ts → index-4g0ZZK3z.d.ts} +33 -0
- package/dist/index.d.mts +289 -31
- package/dist/index.d.ts +289 -31
- package/dist/index.js +328 -213
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +323 -211
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli/verify.js
CHANGED
|
@@ -145,19 +145,126 @@ async function importSearchIndex(data) {
|
|
|
145
145
|
}
|
|
146
146
|
return index;
|
|
147
147
|
}
|
|
148
|
+
var FlexSearchProvider = class {
|
|
149
|
+
name = "flexsearch";
|
|
150
|
+
docs = null;
|
|
151
|
+
searchIndex = null;
|
|
152
|
+
ready = false;
|
|
153
|
+
async initialize(_context, initData) {
|
|
154
|
+
if (!initData) {
|
|
155
|
+
throw new Error("[FlexSearch] SearchProviderInitData required for FlexSearch provider");
|
|
156
|
+
}
|
|
157
|
+
if (initData.docs && initData.indexData) {
|
|
158
|
+
this.docs = initData.docs;
|
|
159
|
+
this.searchIndex = await importSearchIndex(initData.indexData);
|
|
160
|
+
this.ready = true;
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (initData.docsPath && initData.indexPath) {
|
|
164
|
+
if (await fs2__default.default.pathExists(initData.docsPath)) {
|
|
165
|
+
this.docs = await fs2__default.default.readJson(initData.docsPath);
|
|
166
|
+
} else {
|
|
167
|
+
throw new Error(`[FlexSearch] Docs file not found: ${initData.docsPath}`);
|
|
168
|
+
}
|
|
169
|
+
if (await fs2__default.default.pathExists(initData.indexPath)) {
|
|
170
|
+
const indexData = await fs2__default.default.readJson(initData.indexPath);
|
|
171
|
+
this.searchIndex = await importSearchIndex(indexData);
|
|
172
|
+
} else {
|
|
173
|
+
throw new Error(`[FlexSearch] Search index not found: ${initData.indexPath}`);
|
|
174
|
+
}
|
|
175
|
+
this.ready = true;
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
throw new Error(
|
|
179
|
+
"[FlexSearch] Invalid init data: must provide either file paths (docsPath, indexPath) or pre-loaded data (docs, indexData)"
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
isReady() {
|
|
183
|
+
return this.ready && this.docs !== null && this.searchIndex !== null;
|
|
184
|
+
}
|
|
185
|
+
async search(query, options) {
|
|
186
|
+
if (!this.isReady() || !this.docs || !this.searchIndex) {
|
|
187
|
+
throw new Error("[FlexSearch] Provider not initialized");
|
|
188
|
+
}
|
|
189
|
+
const limit = options?.limit ?? 5;
|
|
190
|
+
return searchIndex(this.searchIndex, this.docs, query, { limit });
|
|
191
|
+
}
|
|
192
|
+
async getDocument(route) {
|
|
193
|
+
if (!this.docs) {
|
|
194
|
+
throw new Error("[FlexSearch] Provider not initialized");
|
|
195
|
+
}
|
|
196
|
+
if (this.docs[route]) {
|
|
197
|
+
return this.docs[route];
|
|
198
|
+
}
|
|
199
|
+
const normalizedRoute = route.startsWith("/") ? route : `/${route}`;
|
|
200
|
+
const withoutSlash = route.startsWith("/") ? route.slice(1) : route;
|
|
201
|
+
return this.docs[normalizedRoute] ?? this.docs[withoutSlash] ?? null;
|
|
202
|
+
}
|
|
203
|
+
async healthCheck() {
|
|
204
|
+
if (!this.isReady()) {
|
|
205
|
+
return { healthy: false, message: "FlexSearch provider not initialized" };
|
|
206
|
+
}
|
|
207
|
+
const docCount = this.docs ? Object.keys(this.docs).length : 0;
|
|
208
|
+
return {
|
|
209
|
+
healthy: true,
|
|
210
|
+
message: `FlexSearch provider ready with ${docCount} documents`
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Get all loaded documents (for compatibility with existing server code)
|
|
215
|
+
*/
|
|
216
|
+
getDocs() {
|
|
217
|
+
return this.docs;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Get the FlexSearch index (for compatibility with existing server code)
|
|
221
|
+
*/
|
|
222
|
+
getSearchIndex() {
|
|
223
|
+
return this.searchIndex;
|
|
224
|
+
}
|
|
225
|
+
};
|
|
148
226
|
|
|
149
|
-
// src/
|
|
150
|
-
function
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
227
|
+
// src/providers/loader.ts
|
|
228
|
+
async function loadSearchProvider(specifier) {
|
|
229
|
+
if (specifier === "flexsearch") {
|
|
230
|
+
return new FlexSearchProvider();
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
const module = await import(specifier);
|
|
234
|
+
const ProviderClass = module.default;
|
|
235
|
+
if (typeof ProviderClass === "function") {
|
|
236
|
+
const instance = new ProviderClass();
|
|
237
|
+
if (!isSearchProvider(instance)) {
|
|
238
|
+
throw new Error(
|
|
239
|
+
`Invalid search provider module "${specifier}": does not implement SearchProvider interface`
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
return instance;
|
|
243
|
+
}
|
|
244
|
+
if (isSearchProvider(ProviderClass)) {
|
|
245
|
+
return ProviderClass;
|
|
246
|
+
}
|
|
247
|
+
throw new Error(
|
|
248
|
+
`Invalid search provider module "${specifier}": must export a default class or SearchProvider instance`
|
|
249
|
+
);
|
|
250
|
+
} catch (error) {
|
|
251
|
+
if (error instanceof Error && error.message.includes("Cannot find module")) {
|
|
252
|
+
throw new Error(
|
|
253
|
+
`Search provider module not found: "${specifier}". Check the path or package name.`
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
throw error;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function isSearchProvider(obj) {
|
|
260
|
+
if (!obj || typeof obj !== "object") {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
const provider = obj;
|
|
264
|
+
return typeof provider.name === "string" && typeof provider.initialize === "function" && typeof provider.isReady === "function" && typeof provider.search === "function";
|
|
160
265
|
}
|
|
266
|
+
|
|
267
|
+
// src/mcp/tools/docs-search.ts
|
|
161
268
|
function formatSearchResults(results, baseUrl) {
|
|
162
269
|
if (results.length === 0) {
|
|
163
270
|
return "No matching documents found.";
|
|
@@ -182,29 +289,7 @@ function formatSearchResults(results, baseUrl) {
|
|
|
182
289
|
return lines.join("\n");
|
|
183
290
|
}
|
|
184
291
|
|
|
185
|
-
// src/mcp/tools/docs-
|
|
186
|
-
function executeDocsGetPage(params, docs) {
|
|
187
|
-
const { route } = params;
|
|
188
|
-
if (!route || typeof route !== "string") {
|
|
189
|
-
throw new Error("Route parameter is required and must be a string");
|
|
190
|
-
}
|
|
191
|
-
let normalizedRoute = route.trim();
|
|
192
|
-
if (!normalizedRoute.startsWith("/")) {
|
|
193
|
-
normalizedRoute = "/" + normalizedRoute;
|
|
194
|
-
}
|
|
195
|
-
if (normalizedRoute.length > 1 && normalizedRoute.endsWith("/")) {
|
|
196
|
-
normalizedRoute = normalizedRoute.slice(0, -1);
|
|
197
|
-
}
|
|
198
|
-
const doc = docs[normalizedRoute];
|
|
199
|
-
if (!doc) {
|
|
200
|
-
const altRoute = normalizedRoute.slice(1);
|
|
201
|
-
if (docs[altRoute]) {
|
|
202
|
-
return docs[altRoute] ?? null;
|
|
203
|
-
}
|
|
204
|
-
return null;
|
|
205
|
-
}
|
|
206
|
-
return doc;
|
|
207
|
-
}
|
|
292
|
+
// src/mcp/tools/docs-fetch.ts
|
|
208
293
|
function formatPageContent(doc, baseUrl) {
|
|
209
294
|
if (!doc) {
|
|
210
295
|
return "Page not found. Please check the route path and try again.";
|
|
@@ -239,89 +324,6 @@ function formatPageContent(doc, baseUrl) {
|
|
|
239
324
|
return lines.join("\n");
|
|
240
325
|
}
|
|
241
326
|
|
|
242
|
-
// src/processing/heading-extractor.ts
|
|
243
|
-
function extractSection(markdown, headingId, headings) {
|
|
244
|
-
const heading = headings.find((h) => h.id === headingId);
|
|
245
|
-
if (!heading) {
|
|
246
|
-
return null;
|
|
247
|
-
}
|
|
248
|
-
return markdown.slice(heading.startOffset, heading.endOffset).trim();
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// src/mcp/tools/docs-get-section.ts
|
|
252
|
-
function executeDocsGetSection(params, docs) {
|
|
253
|
-
const { route, headingId } = params;
|
|
254
|
-
if (!route || typeof route !== "string") {
|
|
255
|
-
throw new Error("Route parameter is required and must be a string");
|
|
256
|
-
}
|
|
257
|
-
if (!headingId || typeof headingId !== "string") {
|
|
258
|
-
throw new Error("HeadingId parameter is required and must be a string");
|
|
259
|
-
}
|
|
260
|
-
let normalizedRoute = route.trim();
|
|
261
|
-
if (!normalizedRoute.startsWith("/")) {
|
|
262
|
-
normalizedRoute = "/" + normalizedRoute;
|
|
263
|
-
}
|
|
264
|
-
if (normalizedRoute.length > 1 && normalizedRoute.endsWith("/")) {
|
|
265
|
-
normalizedRoute = normalizedRoute.slice(0, -1);
|
|
266
|
-
}
|
|
267
|
-
const doc = docs[normalizedRoute];
|
|
268
|
-
if (!doc) {
|
|
269
|
-
return {
|
|
270
|
-
content: null,
|
|
271
|
-
doc: null,
|
|
272
|
-
headingText: null,
|
|
273
|
-
availableHeadings: []
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
const availableHeadings = doc.headings.map((h) => ({
|
|
277
|
-
id: h.id,
|
|
278
|
-
text: h.text,
|
|
279
|
-
level: h.level
|
|
280
|
-
}));
|
|
281
|
-
const heading = doc.headings.find((h) => h.id === headingId.trim());
|
|
282
|
-
if (!heading) {
|
|
283
|
-
return {
|
|
284
|
-
content: null,
|
|
285
|
-
doc,
|
|
286
|
-
headingText: null,
|
|
287
|
-
availableHeadings
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
const content = extractSection(doc.markdown, headingId.trim(), doc.headings);
|
|
291
|
-
return {
|
|
292
|
-
content,
|
|
293
|
-
doc,
|
|
294
|
-
headingText: heading.text,
|
|
295
|
-
availableHeadings
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
function formatSectionContent(result, headingId, baseUrl) {
|
|
299
|
-
if (!result.doc) {
|
|
300
|
-
return "Page not found. Please check the route path and try again.";
|
|
301
|
-
}
|
|
302
|
-
if (!result.content) {
|
|
303
|
-
const lines2 = [`Section "${headingId}" not found in this document.`, "", "Available sections:"];
|
|
304
|
-
for (const heading of result.availableHeadings) {
|
|
305
|
-
const indent = " ".repeat(heading.level - 1);
|
|
306
|
-
lines2.push(`${indent}- ${heading.text} (id: ${heading.id})`);
|
|
307
|
-
}
|
|
308
|
-
return lines2.join("\n");
|
|
309
|
-
}
|
|
310
|
-
const lines = [];
|
|
311
|
-
const fullUrl = baseUrl ? `${baseUrl.replace(/\/$/, "")}${result.doc.route}#${headingId}` : null;
|
|
312
|
-
lines.push(`# ${result.headingText}`);
|
|
313
|
-
if (fullUrl) {
|
|
314
|
-
lines.push(`> From: ${result.doc.title} - ${fullUrl}`);
|
|
315
|
-
} else {
|
|
316
|
-
lines.push(`> From: ${result.doc.title} (${result.doc.route})`);
|
|
317
|
-
}
|
|
318
|
-
lines.push("");
|
|
319
|
-
lines.push("---");
|
|
320
|
-
lines.push("");
|
|
321
|
-
lines.push(result.content);
|
|
322
|
-
return lines.join("\n");
|
|
323
|
-
}
|
|
324
|
-
|
|
325
327
|
// src/mcp/server.ts
|
|
326
328
|
function isFileConfig(config) {
|
|
327
329
|
return "docsPath" in config && "indexPath" in config;
|
|
@@ -331,8 +333,7 @@ function isDataConfig(config) {
|
|
|
331
333
|
}
|
|
332
334
|
var McpDocsServer = class {
|
|
333
335
|
config;
|
|
334
|
-
|
|
335
|
-
searchIndex = null;
|
|
336
|
+
searchProvider = null;
|
|
336
337
|
mcpServer;
|
|
337
338
|
initialized = false;
|
|
338
339
|
constructor(config) {
|
|
@@ -365,75 +366,83 @@ var McpDocsServer = class {
|
|
|
365
366
|
},
|
|
366
367
|
async ({ query, limit }) => {
|
|
367
368
|
await this.initialize();
|
|
368
|
-
if (!this.
|
|
369
|
+
if (!this.searchProvider || !this.searchProvider.isReady()) {
|
|
369
370
|
return {
|
|
370
371
|
content: [{ type: "text", text: "Server not initialized. Please try again." }],
|
|
371
372
|
isError: true
|
|
372
373
|
};
|
|
373
374
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
375
|
+
try {
|
|
376
|
+
const results = await this.searchProvider.search(query, { limit });
|
|
377
|
+
return {
|
|
378
|
+
content: [
|
|
379
|
+
{ type: "text", text: formatSearchResults(results, this.config.baseUrl) }
|
|
380
|
+
]
|
|
381
|
+
};
|
|
382
|
+
} catch (error) {
|
|
383
|
+
console.error("[MCP] Search error:", error);
|
|
384
|
+
return {
|
|
385
|
+
content: [{ type: "text", text: `Search error: ${String(error)}` }],
|
|
386
|
+
isError: true
|
|
387
|
+
};
|
|
388
|
+
}
|
|
380
389
|
}
|
|
381
390
|
);
|
|
382
391
|
this.mcpServer.registerTool(
|
|
383
|
-
"
|
|
392
|
+
"docs_fetch",
|
|
384
393
|
{
|
|
385
|
-
description: "
|
|
394
|
+
description: "Fetch the complete content of a documentation page. Use this when you need the full content of a specific page.",
|
|
386
395
|
inputSchema: {
|
|
387
396
|
route: zod.z.string().min(1).describe('The page route path (e.g., "/docs/getting-started" or "/api/reference")')
|
|
388
397
|
}
|
|
389
398
|
},
|
|
390
399
|
async ({ route }) => {
|
|
391
400
|
await this.initialize();
|
|
392
|
-
if (!this.
|
|
401
|
+
if (!this.searchProvider || !this.searchProvider.isReady()) {
|
|
393
402
|
return {
|
|
394
403
|
content: [{ type: "text", text: "Server not initialized. Please try again." }],
|
|
395
404
|
isError: true
|
|
396
405
|
};
|
|
397
406
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
content: [{ type: "text", text: formatPageContent(doc, this.config.baseUrl) }]
|
|
401
|
-
};
|
|
402
|
-
}
|
|
403
|
-
);
|
|
404
|
-
this.mcpServer.registerTool(
|
|
405
|
-
"docs_get_section",
|
|
406
|
-
{
|
|
407
|
-
description: "Retrieve a specific section from a documentation page by its heading ID. Use this when you need only a portion of a page rather than the entire content.",
|
|
408
|
-
inputSchema: {
|
|
409
|
-
route: zod.z.string().min(1).describe("The page route path"),
|
|
410
|
-
headingId: zod.z.string().min(1).describe(
|
|
411
|
-
'The heading ID of the section to extract (e.g., "installation", "api-reference")'
|
|
412
|
-
)
|
|
413
|
-
}
|
|
414
|
-
},
|
|
415
|
-
async ({ route, headingId }) => {
|
|
416
|
-
await this.initialize();
|
|
417
|
-
if (!this.docs) {
|
|
407
|
+
try {
|
|
408
|
+
const doc = await this.getDocument(route);
|
|
418
409
|
return {
|
|
419
|
-
content: [{ type: "text", text:
|
|
410
|
+
content: [{ type: "text", text: formatPageContent(doc, this.config.baseUrl) }]
|
|
411
|
+
};
|
|
412
|
+
} catch (error) {
|
|
413
|
+
console.error("[MCP] Get page error:", error);
|
|
414
|
+
return {
|
|
415
|
+
content: [{ type: "text", text: `Error getting page: ${String(error)}` }],
|
|
420
416
|
isError: true
|
|
421
417
|
};
|
|
422
418
|
}
|
|
423
|
-
const result = executeDocsGetSection({ route, headingId }, this.docs);
|
|
424
|
-
return {
|
|
425
|
-
content: [
|
|
426
|
-
{
|
|
427
|
-
type: "text",
|
|
428
|
-
text: formatSectionContent(result, headingId, this.config.baseUrl)
|
|
429
|
-
}
|
|
430
|
-
]
|
|
431
|
-
};
|
|
432
419
|
}
|
|
433
420
|
);
|
|
434
421
|
}
|
|
435
422
|
/**
|
|
436
|
-
*
|
|
423
|
+
* Get a document by route using the search provider
|
|
424
|
+
*/
|
|
425
|
+
async getDocument(route) {
|
|
426
|
+
if (!this.searchProvider) {
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
if (this.searchProvider.getDocument) {
|
|
430
|
+
return this.searchProvider.getDocument(route);
|
|
431
|
+
}
|
|
432
|
+
if (this.searchProvider instanceof FlexSearchProvider) {
|
|
433
|
+
const docs = this.searchProvider.getDocs();
|
|
434
|
+
if (!docs) return null;
|
|
435
|
+
if (docs[route]) {
|
|
436
|
+
return docs[route];
|
|
437
|
+
}
|
|
438
|
+
const normalizedRoute = route.startsWith("/") ? route : `/${route}`;
|
|
439
|
+
const withoutSlash = route.startsWith("/") ? route.slice(1) : route;
|
|
440
|
+
return docs[normalizedRoute] ?? docs[withoutSlash] ?? null;
|
|
441
|
+
}
|
|
442
|
+
return null;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Load docs and search index using the configured search provider
|
|
437
446
|
*
|
|
438
447
|
* For file-based config: reads from disk
|
|
439
448
|
* For data config: uses pre-loaded data directly
|
|
@@ -443,24 +452,26 @@ var McpDocsServer = class {
|
|
|
443
452
|
return;
|
|
444
453
|
}
|
|
445
454
|
try {
|
|
455
|
+
const searchSpecifier = this.config.search ?? "flexsearch";
|
|
456
|
+
this.searchProvider = await loadSearchProvider(searchSpecifier);
|
|
457
|
+
const providerContext = {
|
|
458
|
+
baseUrl: this.config.baseUrl ?? "",
|
|
459
|
+
serverName: this.config.name,
|
|
460
|
+
serverVersion: this.config.version ?? "1.0.0",
|
|
461
|
+
outputDir: ""
|
|
462
|
+
// Not relevant for runtime
|
|
463
|
+
};
|
|
464
|
+
const initData = {};
|
|
446
465
|
if (isDataConfig(this.config)) {
|
|
447
|
-
|
|
448
|
-
|
|
466
|
+
initData.docs = this.config.docs;
|
|
467
|
+
initData.indexData = this.config.searchIndexData;
|
|
449
468
|
} else if (isFileConfig(this.config)) {
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
} else {
|
|
453
|
-
throw new Error(`Docs file not found: ${this.config.docsPath}`);
|
|
454
|
-
}
|
|
455
|
-
if (await fs2__default.default.pathExists(this.config.indexPath)) {
|
|
456
|
-
const indexData = await fs2__default.default.readJson(this.config.indexPath);
|
|
457
|
-
this.searchIndex = await importSearchIndex(indexData);
|
|
458
|
-
} else {
|
|
459
|
-
throw new Error(`Search index not found: ${this.config.indexPath}`);
|
|
460
|
-
}
|
|
469
|
+
initData.docsPath = this.config.docsPath;
|
|
470
|
+
initData.indexPath = this.config.indexPath;
|
|
461
471
|
} else {
|
|
462
472
|
throw new Error("Invalid server config: must provide either file paths or pre-loaded data");
|
|
463
473
|
}
|
|
474
|
+
await this.searchProvider.initialize(providerContext, initData);
|
|
464
475
|
this.initialized = true;
|
|
465
476
|
} catch (error) {
|
|
466
477
|
console.error("[MCP] Failed to initialize:", error);
|
|
@@ -521,12 +532,18 @@ var McpDocsServer = class {
|
|
|
521
532
|
* Useful for health checks and debugging
|
|
522
533
|
*/
|
|
523
534
|
async getStatus() {
|
|
535
|
+
let docCount = 0;
|
|
536
|
+
if (this.searchProvider instanceof FlexSearchProvider) {
|
|
537
|
+
const docs = this.searchProvider.getDocs();
|
|
538
|
+
docCount = docs ? Object.keys(docs).length : 0;
|
|
539
|
+
}
|
|
524
540
|
return {
|
|
525
541
|
name: this.config.name,
|
|
526
542
|
version: this.config.version ?? "1.0.0",
|
|
527
543
|
initialized: this.initialized,
|
|
528
|
-
docCount
|
|
529
|
-
baseUrl: this.config.baseUrl
|
|
544
|
+
docCount,
|
|
545
|
+
baseUrl: this.config.baseUrl,
|
|
546
|
+
searchProvider: this.searchProvider?.name
|
|
530
547
|
};
|
|
531
548
|
}
|
|
532
549
|
/**
|