docusaurus-plugin-mcp-server 0.11.0 → 0.12.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/README.md +108 -30
- package/dist/adapters-entry.d.ts +9 -3
- package/dist/adapters-entry.js +242 -181
- package/dist/adapters-entry.js.map +1 -1
- package/dist/cli/verify.js +185 -129
- package/dist/cli/verify.js.map +1 -1
- package/dist/{index-BB96x1Rw.d.ts → index-BhwLRGxJ.d.ts} +2 -31
- package/dist/index.d.ts +22 -168
- package/dist/index.js +404 -343
- package/dist/index.js.map +1 -1
- package/package.json +5 -8
package/dist/cli/verify.js
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import FlexSearch from 'flexsearch';
|
|
2
3
|
import fs2 from 'fs-extra';
|
|
3
4
|
import path from 'path';
|
|
4
5
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
5
6
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
6
7
|
import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
|
|
7
|
-
import FlexSearch from 'flexsearch';
|
|
8
8
|
import { z } from 'zod';
|
|
9
9
|
|
|
10
|
-
var
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
var __defProp = Object.defineProperty;
|
|
11
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
12
|
+
var __esm = (fn, res) => function __init() {
|
|
13
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
14
|
+
};
|
|
15
|
+
var __export = (target, all) => {
|
|
16
|
+
for (var name in all)
|
|
17
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
18
|
};
|
|
16
19
|
function englishStemmer(word) {
|
|
17
20
|
if (word.length <= 3) return word;
|
|
@@ -47,7 +50,7 @@ function createSearchIndex() {
|
|
|
47
50
|
}
|
|
48
51
|
});
|
|
49
52
|
}
|
|
50
|
-
function
|
|
53
|
+
function querySearchIndex(index, docs, query, options = {}) {
|
|
51
54
|
const { limit = 16 } = options;
|
|
52
55
|
const rawResults = index.search(query, {
|
|
53
56
|
limit: limit * 3,
|
|
@@ -139,84 +142,111 @@ async function importSearchIndex(data) {
|
|
|
139
142
|
}
|
|
140
143
|
return index;
|
|
141
144
|
}
|
|
142
|
-
var
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
145
|
+
var FIELD_WEIGHTS;
|
|
146
|
+
var init_flexsearch_core = __esm({
|
|
147
|
+
"src/search/flexsearch-core.ts"() {
|
|
148
|
+
FIELD_WEIGHTS = {
|
|
149
|
+
title: 3,
|
|
150
|
+
headings: 2,
|
|
151
|
+
description: 1.5,
|
|
152
|
+
content: 1
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// src/providers/search/flexsearch-provider.ts
|
|
158
|
+
var flexsearch_provider_exports = {};
|
|
159
|
+
__export(flexsearch_provider_exports, {
|
|
160
|
+
FlexSearchProvider: () => FlexSearchProvider
|
|
161
|
+
});
|
|
162
|
+
var FlexSearchProvider;
|
|
163
|
+
var init_flexsearch_provider = __esm({
|
|
164
|
+
"src/providers/search/flexsearch-provider.ts"() {
|
|
165
|
+
init_flexsearch_core();
|
|
166
|
+
FlexSearchProvider = class {
|
|
167
|
+
name = "flexsearch";
|
|
168
|
+
docs = null;
|
|
169
|
+
searchIndex = null;
|
|
170
|
+
ready = false;
|
|
171
|
+
async initialize(_context, initData) {
|
|
172
|
+
if (!initData) {
|
|
173
|
+
throw new Error("[FlexSearch] SearchProviderInitData required for FlexSearch provider");
|
|
174
|
+
}
|
|
175
|
+
if (initData.docs && initData.indexData) {
|
|
176
|
+
this.docs = initData.docs;
|
|
177
|
+
this.searchIndex = await importSearchIndex(initData.indexData);
|
|
178
|
+
this.ready = true;
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (initData.docsPath && initData.indexPath) {
|
|
182
|
+
if (await fs2.pathExists(initData.docsPath)) {
|
|
183
|
+
this.docs = await fs2.readJson(initData.docsPath);
|
|
184
|
+
} else {
|
|
185
|
+
throw new Error(`[FlexSearch] Docs file not found: ${initData.docsPath}`);
|
|
186
|
+
}
|
|
187
|
+
if (await fs2.pathExists(initData.indexPath)) {
|
|
188
|
+
const indexData = await fs2.readJson(initData.indexPath);
|
|
189
|
+
this.searchIndex = await importSearchIndex(indexData);
|
|
190
|
+
} else {
|
|
191
|
+
throw new Error(`[FlexSearch] Search index not found: ${initData.indexPath}`);
|
|
192
|
+
}
|
|
193
|
+
this.ready = true;
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
throw new Error(
|
|
197
|
+
"[FlexSearch] Invalid init data: must provide either file paths (docsPath, indexPath) or pre-loaded data (docs, indexData)"
|
|
198
|
+
);
|
|
162
199
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
200
|
+
isReady() {
|
|
201
|
+
return this.ready && this.docs !== null && this.searchIndex !== null;
|
|
202
|
+
}
|
|
203
|
+
async search(query, options) {
|
|
204
|
+
if (!this.isReady() || !this.docs || !this.searchIndex) {
|
|
205
|
+
throw new Error("[FlexSearch] Provider not initialized");
|
|
206
|
+
}
|
|
207
|
+
const limit = options?.limit ?? 16;
|
|
208
|
+
return querySearchIndex(this.searchIndex, this.docs, query, { limit });
|
|
209
|
+
}
|
|
210
|
+
async getDocument(url) {
|
|
211
|
+
if (!this.docs) {
|
|
212
|
+
throw new Error("[FlexSearch] Provider not initialized");
|
|
213
|
+
}
|
|
214
|
+
return this.docs[url] ?? null;
|
|
215
|
+
}
|
|
216
|
+
async healthCheck() {
|
|
217
|
+
if (!this.isReady()) {
|
|
218
|
+
return { healthy: false, message: "FlexSearch provider not initialized" };
|
|
219
|
+
}
|
|
220
|
+
const docCount = this.docs ? Object.keys(this.docs).length : 0;
|
|
221
|
+
return {
|
|
222
|
+
healthy: true,
|
|
223
|
+
message: `FlexSearch provider ready with ${docCount} documents`
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
getDocCount() {
|
|
227
|
+
return this.docs ? Object.keys(this.docs).length : 0;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Get all loaded documents (for compatibility with existing server code)
|
|
231
|
+
*/
|
|
232
|
+
getDocs() {
|
|
233
|
+
return this.docs;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Get the FlexSearch index (for compatibility with existing server code)
|
|
237
|
+
*/
|
|
238
|
+
getSearchIndex() {
|
|
239
|
+
return this.searchIndex;
|
|
168
240
|
}
|
|
169
|
-
this.ready = true;
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
throw new Error(
|
|
173
|
-
"[FlexSearch] Invalid init data: must provide either file paths (docsPath, indexPath) or pre-loaded data (docs, indexData)"
|
|
174
|
-
);
|
|
175
|
-
}
|
|
176
|
-
isReady() {
|
|
177
|
-
return this.ready && this.docs !== null && this.searchIndex !== null;
|
|
178
|
-
}
|
|
179
|
-
async search(query, options) {
|
|
180
|
-
if (!this.isReady() || !this.docs || !this.searchIndex) {
|
|
181
|
-
throw new Error("[FlexSearch] Provider not initialized");
|
|
182
|
-
}
|
|
183
|
-
const limit = options?.limit ?? 16;
|
|
184
|
-
return searchIndex(this.searchIndex, this.docs, query, { limit });
|
|
185
|
-
}
|
|
186
|
-
async getDocument(url) {
|
|
187
|
-
if (!this.docs) {
|
|
188
|
-
throw new Error("[FlexSearch] Provider not initialized");
|
|
189
|
-
}
|
|
190
|
-
return this.docs[url] ?? null;
|
|
191
|
-
}
|
|
192
|
-
async healthCheck() {
|
|
193
|
-
if (!this.isReady()) {
|
|
194
|
-
return { healthy: false, message: "FlexSearch provider not initialized" };
|
|
195
|
-
}
|
|
196
|
-
const docCount = this.docs ? Object.keys(this.docs).length : 0;
|
|
197
|
-
return {
|
|
198
|
-
healthy: true,
|
|
199
|
-
message: `FlexSearch provider ready with ${docCount} documents`
|
|
200
241
|
};
|
|
201
242
|
}
|
|
202
|
-
|
|
203
|
-
* Get all loaded documents (for compatibility with existing server code)
|
|
204
|
-
*/
|
|
205
|
-
getDocs() {
|
|
206
|
-
return this.docs;
|
|
207
|
-
}
|
|
208
|
-
/**
|
|
209
|
-
* Get the FlexSearch index (for compatibility with existing server code)
|
|
210
|
-
*/
|
|
211
|
-
getSearchIndex() {
|
|
212
|
-
return this.searchIndex;
|
|
213
|
-
}
|
|
214
|
-
};
|
|
243
|
+
});
|
|
215
244
|
|
|
216
245
|
// src/providers/loader.ts
|
|
217
246
|
async function loadSearchProvider(specifier) {
|
|
218
247
|
if (specifier === "flexsearch") {
|
|
219
|
-
|
|
248
|
+
const { FlexSearchProvider: FlexSearchProvider2 } = await Promise.resolve().then(() => (init_flexsearch_provider(), flexsearch_provider_exports));
|
|
249
|
+
return new FlexSearchProvider2();
|
|
220
250
|
}
|
|
221
251
|
try {
|
|
222
252
|
const module = await import(specifier);
|
|
@@ -239,7 +269,8 @@ async function loadSearchProvider(specifier) {
|
|
|
239
269
|
} catch (error) {
|
|
240
270
|
if (error instanceof Error && error.message.includes("Cannot find module")) {
|
|
241
271
|
throw new Error(
|
|
242
|
-
`Search provider module not found: "${specifier}". Check the path or package name
|
|
272
|
+
`Search provider module not found: "${specifier}". Check the path or package name.`,
|
|
273
|
+
{ cause: error }
|
|
243
274
|
);
|
|
244
275
|
}
|
|
245
276
|
throw error;
|
|
@@ -252,6 +283,9 @@ function isSearchProvider(obj) {
|
|
|
252
283
|
const provider = obj;
|
|
253
284
|
return typeof provider.name === "string" && typeof provider.initialize === "function" && typeof provider.isReady === "function" && typeof provider.search === "function";
|
|
254
285
|
}
|
|
286
|
+
|
|
287
|
+
// src/mcp/tools/docs-search.ts
|
|
288
|
+
init_flexsearch_core();
|
|
255
289
|
var docsSearchInputSchema = {
|
|
256
290
|
query: z.string().min(1).describe("The search query string"),
|
|
257
291
|
limit: z.number().int().min(1).max(20).optional().default(16).describe("Maximum number of results to return (1-20, default: 16)")
|
|
@@ -329,14 +363,22 @@ function isDataConfig(config) {
|
|
|
329
363
|
var McpDocsServer = class {
|
|
330
364
|
config;
|
|
331
365
|
searchProvider = null;
|
|
332
|
-
mcpServer;
|
|
333
366
|
initialized = false;
|
|
367
|
+
initPromise = null;
|
|
368
|
+
initError = null;
|
|
334
369
|
constructor(config) {
|
|
335
370
|
this.config = config;
|
|
336
|
-
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Create a fresh McpServer instance with tools registered.
|
|
374
|
+
* Each request gets its own server to avoid concurrency issues
|
|
375
|
+
* with the SDK's transport reassignment.
|
|
376
|
+
*/
|
|
377
|
+
createMcpServer() {
|
|
378
|
+
const server = new McpServer(
|
|
337
379
|
{
|
|
338
|
-
name: config.name,
|
|
339
|
-
version: config.version ?? "1.0.0"
|
|
380
|
+
name: this.config.name,
|
|
381
|
+
version: this.config.version ?? "1.0.0"
|
|
340
382
|
},
|
|
341
383
|
{
|
|
342
384
|
capabilities: {
|
|
@@ -344,20 +386,20 @@ var McpDocsServer = class {
|
|
|
344
386
|
}
|
|
345
387
|
}
|
|
346
388
|
);
|
|
347
|
-
this.registerTools();
|
|
389
|
+
this.registerTools(server);
|
|
390
|
+
return server;
|
|
348
391
|
}
|
|
349
392
|
/**
|
|
350
393
|
* Register all MCP tools using definitions from tool files
|
|
351
394
|
*/
|
|
352
|
-
registerTools() {
|
|
353
|
-
|
|
395
|
+
registerTools(server) {
|
|
396
|
+
server.registerTool(
|
|
354
397
|
docsSearchTool.name,
|
|
355
398
|
{
|
|
356
399
|
description: docsSearchTool.description,
|
|
357
400
|
inputSchema: docsSearchTool.inputSchema
|
|
358
401
|
},
|
|
359
402
|
async ({ query, limit }) => {
|
|
360
|
-
await this.initialize();
|
|
361
403
|
if (!this.searchProvider || !this.searchProvider.isReady()) {
|
|
362
404
|
return {
|
|
363
405
|
content: [{ type: "text", text: "Server not initialized. Please try again." }],
|
|
@@ -372,20 +414,24 @@ var McpDocsServer = class {
|
|
|
372
414
|
} catch (error) {
|
|
373
415
|
console.error("[MCP] Search error:", error);
|
|
374
416
|
return {
|
|
375
|
-
content: [
|
|
417
|
+
content: [
|
|
418
|
+
{
|
|
419
|
+
type: "text",
|
|
420
|
+
text: "An error occurred while searching. Please try again."
|
|
421
|
+
}
|
|
422
|
+
],
|
|
376
423
|
isError: true
|
|
377
424
|
};
|
|
378
425
|
}
|
|
379
426
|
}
|
|
380
427
|
);
|
|
381
|
-
|
|
428
|
+
server.registerTool(
|
|
382
429
|
docsFetchTool.name,
|
|
383
430
|
{
|
|
384
431
|
description: docsFetchTool.description,
|
|
385
432
|
inputSchema: docsFetchTool.inputSchema
|
|
386
433
|
},
|
|
387
434
|
async ({ url }) => {
|
|
388
|
-
await this.initialize();
|
|
389
435
|
if (!this.searchProvider || !this.searchProvider.isReady()) {
|
|
390
436
|
return {
|
|
391
437
|
content: [{ type: "text", text: "Server not initialized. Please try again." }],
|
|
@@ -400,7 +446,12 @@ var McpDocsServer = class {
|
|
|
400
446
|
} catch (error) {
|
|
401
447
|
console.error("[MCP] Fetch error:", error);
|
|
402
448
|
return {
|
|
403
|
-
content: [
|
|
449
|
+
content: [
|
|
450
|
+
{
|
|
451
|
+
type: "text",
|
|
452
|
+
text: "An error occurred while fetching the page. Please try again."
|
|
453
|
+
}
|
|
454
|
+
],
|
|
404
455
|
isError: true
|
|
405
456
|
};
|
|
406
457
|
}
|
|
@@ -424,43 +475,54 @@ var McpDocsServer = class {
|
|
|
424
475
|
*
|
|
425
476
|
* For file-based config: reads from disk
|
|
426
477
|
* For data config: uses pre-loaded data directly
|
|
478
|
+
*
|
|
479
|
+
* Uses promise-based locking to prevent concurrent initialization.
|
|
480
|
+
* Caches initialization errors so repeated calls fail fast.
|
|
427
481
|
*/
|
|
428
482
|
async initialize() {
|
|
483
|
+
if (this.initError) {
|
|
484
|
+
throw this.initError;
|
|
485
|
+
}
|
|
429
486
|
if (this.initialized) {
|
|
430
487
|
return;
|
|
431
488
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
serverVersion: this.config.version ?? "1.0.0",
|
|
439
|
-
outputDir: ""
|
|
440
|
-
// Not relevant for runtime
|
|
441
|
-
};
|
|
442
|
-
const initData = {};
|
|
443
|
-
if (isDataConfig(this.config)) {
|
|
444
|
-
initData.docs = this.config.docs;
|
|
445
|
-
initData.indexData = this.config.searchIndexData;
|
|
446
|
-
} else if (isFileConfig(this.config)) {
|
|
447
|
-
initData.docsPath = this.config.docsPath;
|
|
448
|
-
initData.indexPath = this.config.indexPath;
|
|
449
|
-
} else {
|
|
450
|
-
throw new Error("Invalid server config: must provide either file paths or pre-loaded data");
|
|
451
|
-
}
|
|
452
|
-
await this.searchProvider.initialize(providerContext, initData);
|
|
453
|
-
this.initialized = true;
|
|
454
|
-
} catch (error) {
|
|
455
|
-
console.error("[MCP] Failed to initialize:", error);
|
|
456
|
-
throw error;
|
|
489
|
+
if (!this.initPromise) {
|
|
490
|
+
this.initPromise = this._doInitialize().catch((error) => {
|
|
491
|
+
this.initError = error instanceof Error ? error : new Error(String(error));
|
|
492
|
+
this.initPromise = null;
|
|
493
|
+
throw this.initError;
|
|
494
|
+
});
|
|
457
495
|
}
|
|
496
|
+
return this.initPromise;
|
|
497
|
+
}
|
|
498
|
+
async _doInitialize() {
|
|
499
|
+
const searchSpecifier = this.config.search ?? "flexsearch";
|
|
500
|
+
this.searchProvider = await loadSearchProvider(searchSpecifier);
|
|
501
|
+
const providerContext = {
|
|
502
|
+
baseUrl: this.config.baseUrl ?? "",
|
|
503
|
+
serverName: this.config.name,
|
|
504
|
+
serverVersion: this.config.version ?? "1.0.0",
|
|
505
|
+
outputDir: ""
|
|
506
|
+
// Not relevant for runtime
|
|
507
|
+
};
|
|
508
|
+
const initData = {};
|
|
509
|
+
if (isDataConfig(this.config)) {
|
|
510
|
+
initData.docs = this.config.docs;
|
|
511
|
+
initData.indexData = this.config.searchIndexData;
|
|
512
|
+
} else if (isFileConfig(this.config)) {
|
|
513
|
+
initData.docsPath = this.config.docsPath;
|
|
514
|
+
initData.indexPath = this.config.indexPath;
|
|
515
|
+
} else {
|
|
516
|
+
throw new Error("Invalid server config: must provide either file paths or pre-loaded data");
|
|
517
|
+
}
|
|
518
|
+
await this.searchProvider.initialize(providerContext, initData);
|
|
519
|
+
this.initialized = true;
|
|
458
520
|
}
|
|
459
521
|
/**
|
|
460
522
|
* Handle an HTTP request using the MCP SDK's transport
|
|
461
523
|
*
|
|
462
524
|
* This method is designed for serverless environments (Vercel, Netlify).
|
|
463
|
-
*
|
|
525
|
+
* Creates a fresh McpServer per request to avoid concurrency issues.
|
|
464
526
|
*
|
|
465
527
|
* @param req - Node.js IncomingMessage or compatible request object
|
|
466
528
|
* @param res - Node.js ServerResponse or compatible response object
|
|
@@ -468,13 +530,14 @@ var McpDocsServer = class {
|
|
|
468
530
|
*/
|
|
469
531
|
async handleHttpRequest(req, res, parsedBody) {
|
|
470
532
|
await this.initialize();
|
|
533
|
+
const server = this.createMcpServer();
|
|
471
534
|
const transport = new StreamableHTTPServerTransport({
|
|
472
535
|
sessionIdGenerator: void 0,
|
|
473
536
|
// Stateless mode - no session tracking
|
|
474
537
|
enableJsonResponse: true
|
|
475
538
|
// Return JSON instead of SSE streams
|
|
476
539
|
});
|
|
477
|
-
await
|
|
540
|
+
await server.connect(transport);
|
|
478
541
|
try {
|
|
479
542
|
await transport.handleRequest(req, res, parsedBody);
|
|
480
543
|
} finally {
|
|
@@ -486,18 +549,20 @@ var McpDocsServer = class {
|
|
|
486
549
|
*
|
|
487
550
|
* This method is designed for Web Standard environments that use
|
|
488
551
|
* the Fetch API Request/Response pattern.
|
|
552
|
+
* Creates a fresh McpServer per request to avoid concurrency issues.
|
|
489
553
|
*
|
|
490
554
|
* @param request - Web Standard Request object
|
|
491
555
|
* @returns Web Standard Response object
|
|
492
556
|
*/
|
|
493
557
|
async handleWebRequest(request) {
|
|
494
558
|
await this.initialize();
|
|
559
|
+
const server = this.createMcpServer();
|
|
495
560
|
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
496
561
|
sessionIdGenerator: void 0,
|
|
497
562
|
// Stateless mode
|
|
498
563
|
enableJsonResponse: true
|
|
499
564
|
});
|
|
500
|
-
await
|
|
565
|
+
await server.connect(transport);
|
|
501
566
|
try {
|
|
502
567
|
return await transport.handleRequest(request);
|
|
503
568
|
} finally {
|
|
@@ -511,9 +576,8 @@ var McpDocsServer = class {
|
|
|
511
576
|
*/
|
|
512
577
|
async getStatus() {
|
|
513
578
|
let docCount = 0;
|
|
514
|
-
if (this.searchProvider
|
|
515
|
-
|
|
516
|
-
docCount = docs ? Object.keys(docs).length : 0;
|
|
579
|
+
if (this.searchProvider?.getDocCount) {
|
|
580
|
+
docCount = this.searchProvider.getDocCount();
|
|
517
581
|
}
|
|
518
582
|
return {
|
|
519
583
|
name: this.config.name,
|
|
@@ -524,14 +588,6 @@ var McpDocsServer = class {
|
|
|
524
588
|
searchProvider: this.searchProvider?.name
|
|
525
589
|
};
|
|
526
590
|
}
|
|
527
|
-
/**
|
|
528
|
-
* Get the underlying McpServer instance
|
|
529
|
-
*
|
|
530
|
-
* Useful for advanced use cases like custom transports
|
|
531
|
-
*/
|
|
532
|
-
getMcpServer() {
|
|
533
|
-
return this.mcpServer;
|
|
534
|
-
}
|
|
535
591
|
};
|
|
536
592
|
|
|
537
593
|
// src/cli/verify.ts
|