docusaurus-plugin-mcp-server 0.10.2 → 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 +110 -32
- package/dist/adapters-entry.d.ts +9 -3
- package/dist/adapters-entry.js +247 -186
- package/dist/adapters-entry.js.map +1 -1
- package/dist/cli/verify.js +190 -134
- package/dist/cli/verify.js.map +1 -1
- package/dist/{index-j-CdaS6k.d.ts → index-BhwLRGxJ.d.ts} +3 -32
- package/dist/index.d.ts +22 -168
- package/dist/index.js +405 -344
- package/dist/index.js.map +1 -1
- package/package.json +5 -8
package/dist/adapters-entry.js
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
|
+
import FlexSearch from 'flexsearch';
|
|
2
|
+
import fs from 'fs-extra';
|
|
1
3
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
4
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
3
5
|
import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
|
|
4
|
-
import FlexSearch from 'flexsearch';
|
|
5
|
-
import fs from 'fs-extra';
|
|
6
6
|
import { z } from 'zod';
|
|
7
7
|
import { createServer } from 'http';
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
var
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
var __defProp = Object.defineProperty;
|
|
10
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
11
|
+
var __esm = (fn, res) => function __init() {
|
|
12
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
13
|
+
};
|
|
14
|
+
var __export = (target, all) => {
|
|
15
|
+
for (var name in all)
|
|
16
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
17
|
};
|
|
16
18
|
function englishStemmer(word) {
|
|
17
19
|
if (word.length <= 3) return word;
|
|
@@ -19,9 +21,9 @@ function englishStemmer(word) {
|
|
|
19
21
|
}
|
|
20
22
|
function createSearchIndex() {
|
|
21
23
|
return new FlexSearch.Document({
|
|
22
|
-
// Use '
|
|
23
|
-
//
|
|
24
|
-
tokenize: "
|
|
24
|
+
// Use 'forward' tokenization to avoid OOM with large doc sets
|
|
25
|
+
// See: https://github.com/scalvert/docusaurus-plugin-mcp-server/issues/11
|
|
26
|
+
tokenize: "forward",
|
|
25
27
|
// Enable caching for faster repeated queries
|
|
26
28
|
cache: 100,
|
|
27
29
|
// Higher resolution = more granular ranking (1-9)
|
|
@@ -47,8 +49,8 @@ function createSearchIndex() {
|
|
|
47
49
|
}
|
|
48
50
|
});
|
|
49
51
|
}
|
|
50
|
-
function
|
|
51
|
-
const { limit =
|
|
52
|
+
function querySearchIndex(index, docs, query, options = {}) {
|
|
53
|
+
const { limit = 16 } = options;
|
|
52
54
|
const rawResults = index.search(query, {
|
|
53
55
|
limit: limit * 3,
|
|
54
56
|
// Get extra results for better ranking after weighting
|
|
@@ -139,84 +141,111 @@ async function importSearchIndex(data) {
|
|
|
139
141
|
}
|
|
140
142
|
return index;
|
|
141
143
|
}
|
|
142
|
-
var
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
144
|
+
var FIELD_WEIGHTS;
|
|
145
|
+
var init_flexsearch_core = __esm({
|
|
146
|
+
"src/search/flexsearch-core.ts"() {
|
|
147
|
+
FIELD_WEIGHTS = {
|
|
148
|
+
title: 3,
|
|
149
|
+
headings: 2,
|
|
150
|
+
description: 1.5,
|
|
151
|
+
content: 1
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// src/providers/search/flexsearch-provider.ts
|
|
157
|
+
var flexsearch_provider_exports = {};
|
|
158
|
+
__export(flexsearch_provider_exports, {
|
|
159
|
+
FlexSearchProvider: () => FlexSearchProvider
|
|
160
|
+
});
|
|
161
|
+
var FlexSearchProvider;
|
|
162
|
+
var init_flexsearch_provider = __esm({
|
|
163
|
+
"src/providers/search/flexsearch-provider.ts"() {
|
|
164
|
+
init_flexsearch_core();
|
|
165
|
+
FlexSearchProvider = class {
|
|
166
|
+
name = "flexsearch";
|
|
167
|
+
docs = null;
|
|
168
|
+
searchIndex = null;
|
|
169
|
+
ready = false;
|
|
170
|
+
async initialize(_context, initData) {
|
|
171
|
+
if (!initData) {
|
|
172
|
+
throw new Error("[FlexSearch] SearchProviderInitData required for FlexSearch provider");
|
|
173
|
+
}
|
|
174
|
+
if (initData.docs && initData.indexData) {
|
|
175
|
+
this.docs = initData.docs;
|
|
176
|
+
this.searchIndex = await importSearchIndex(initData.indexData);
|
|
177
|
+
this.ready = true;
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (initData.docsPath && initData.indexPath) {
|
|
181
|
+
if (await fs.pathExists(initData.docsPath)) {
|
|
182
|
+
this.docs = await fs.readJson(initData.docsPath);
|
|
183
|
+
} else {
|
|
184
|
+
throw new Error(`[FlexSearch] Docs file not found: ${initData.docsPath}`);
|
|
185
|
+
}
|
|
186
|
+
if (await fs.pathExists(initData.indexPath)) {
|
|
187
|
+
const indexData = await fs.readJson(initData.indexPath);
|
|
188
|
+
this.searchIndex = await importSearchIndex(indexData);
|
|
189
|
+
} else {
|
|
190
|
+
throw new Error(`[FlexSearch] Search index not found: ${initData.indexPath}`);
|
|
191
|
+
}
|
|
192
|
+
this.ready = true;
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
throw new Error(
|
|
196
|
+
"[FlexSearch] Invalid init data: must provide either file paths (docsPath, indexPath) or pre-loaded data (docs, indexData)"
|
|
197
|
+
);
|
|
162
198
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
199
|
+
isReady() {
|
|
200
|
+
return this.ready && this.docs !== null && this.searchIndex !== null;
|
|
201
|
+
}
|
|
202
|
+
async search(query, options) {
|
|
203
|
+
if (!this.isReady() || !this.docs || !this.searchIndex) {
|
|
204
|
+
throw new Error("[FlexSearch] Provider not initialized");
|
|
205
|
+
}
|
|
206
|
+
const limit = options?.limit ?? 16;
|
|
207
|
+
return querySearchIndex(this.searchIndex, this.docs, query, { limit });
|
|
208
|
+
}
|
|
209
|
+
async getDocument(url) {
|
|
210
|
+
if (!this.docs) {
|
|
211
|
+
throw new Error("[FlexSearch] Provider not initialized");
|
|
212
|
+
}
|
|
213
|
+
return this.docs[url] ?? null;
|
|
214
|
+
}
|
|
215
|
+
async healthCheck() {
|
|
216
|
+
if (!this.isReady()) {
|
|
217
|
+
return { healthy: false, message: "FlexSearch provider not initialized" };
|
|
218
|
+
}
|
|
219
|
+
const docCount = this.docs ? Object.keys(this.docs).length : 0;
|
|
220
|
+
return {
|
|
221
|
+
healthy: true,
|
|
222
|
+
message: `FlexSearch provider ready with ${docCount} documents`
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
getDocCount() {
|
|
226
|
+
return this.docs ? Object.keys(this.docs).length : 0;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Get all loaded documents (for compatibility with existing server code)
|
|
230
|
+
*/
|
|
231
|
+
getDocs() {
|
|
232
|
+
return this.docs;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Get the FlexSearch index (for compatibility with existing server code)
|
|
236
|
+
*/
|
|
237
|
+
getSearchIndex() {
|
|
238
|
+
return this.searchIndex;
|
|
168
239
|
}
|
|
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 ?? 5;
|
|
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
240
|
};
|
|
201
241
|
}
|
|
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
|
-
};
|
|
242
|
+
});
|
|
215
243
|
|
|
216
244
|
// src/providers/loader.ts
|
|
217
245
|
async function loadSearchProvider(specifier) {
|
|
218
246
|
if (specifier === "flexsearch") {
|
|
219
|
-
|
|
247
|
+
const { FlexSearchProvider: FlexSearchProvider2 } = await Promise.resolve().then(() => (init_flexsearch_provider(), flexsearch_provider_exports));
|
|
248
|
+
return new FlexSearchProvider2();
|
|
220
249
|
}
|
|
221
250
|
try {
|
|
222
251
|
const module = await import(specifier);
|
|
@@ -239,7 +268,8 @@ async function loadSearchProvider(specifier) {
|
|
|
239
268
|
} catch (error) {
|
|
240
269
|
if (error instanceof Error && error.message.includes("Cannot find module")) {
|
|
241
270
|
throw new Error(
|
|
242
|
-
`Search provider module not found: "${specifier}". Check the path or package name
|
|
271
|
+
`Search provider module not found: "${specifier}". Check the path or package name.`,
|
|
272
|
+
{ cause: error }
|
|
243
273
|
);
|
|
244
274
|
}
|
|
245
275
|
throw error;
|
|
@@ -252,9 +282,12 @@ function isSearchProvider(obj) {
|
|
|
252
282
|
const provider = obj;
|
|
253
283
|
return typeof provider.name === "string" && typeof provider.initialize === "function" && typeof provider.isReady === "function" && typeof provider.search === "function";
|
|
254
284
|
}
|
|
285
|
+
|
|
286
|
+
// src/mcp/tools/docs-search.ts
|
|
287
|
+
init_flexsearch_core();
|
|
255
288
|
var docsSearchInputSchema = {
|
|
256
289
|
query: z.string().min(1).describe("The search query string"),
|
|
257
|
-
limit: z.number().int().min(1).max(20).optional().default(
|
|
290
|
+
limit: z.number().int().min(1).max(20).optional().default(16).describe("Maximum number of results to return (1-20, default: 16)")
|
|
258
291
|
};
|
|
259
292
|
var docsSearchTool = {
|
|
260
293
|
name: "docs_search",
|
|
@@ -329,14 +362,22 @@ function isDataConfig(config) {
|
|
|
329
362
|
var McpDocsServer = class {
|
|
330
363
|
config;
|
|
331
364
|
searchProvider = null;
|
|
332
|
-
mcpServer;
|
|
333
365
|
initialized = false;
|
|
366
|
+
initPromise = null;
|
|
367
|
+
initError = null;
|
|
334
368
|
constructor(config) {
|
|
335
369
|
this.config = config;
|
|
336
|
-
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Create a fresh McpServer instance with tools registered.
|
|
373
|
+
* Each request gets its own server to avoid concurrency issues
|
|
374
|
+
* with the SDK's transport reassignment.
|
|
375
|
+
*/
|
|
376
|
+
createMcpServer() {
|
|
377
|
+
const server = new McpServer(
|
|
337
378
|
{
|
|
338
|
-
name: config.name,
|
|
339
|
-
version: config.version ?? "1.0.0"
|
|
379
|
+
name: this.config.name,
|
|
380
|
+
version: this.config.version ?? "1.0.0"
|
|
340
381
|
},
|
|
341
382
|
{
|
|
342
383
|
capabilities: {
|
|
@@ -344,20 +385,20 @@ var McpDocsServer = class {
|
|
|
344
385
|
}
|
|
345
386
|
}
|
|
346
387
|
);
|
|
347
|
-
this.registerTools();
|
|
388
|
+
this.registerTools(server);
|
|
389
|
+
return server;
|
|
348
390
|
}
|
|
349
391
|
/**
|
|
350
392
|
* Register all MCP tools using definitions from tool files
|
|
351
393
|
*/
|
|
352
|
-
registerTools() {
|
|
353
|
-
|
|
394
|
+
registerTools(server) {
|
|
395
|
+
server.registerTool(
|
|
354
396
|
docsSearchTool.name,
|
|
355
397
|
{
|
|
356
398
|
description: docsSearchTool.description,
|
|
357
399
|
inputSchema: docsSearchTool.inputSchema
|
|
358
400
|
},
|
|
359
401
|
async ({ query, limit }) => {
|
|
360
|
-
await this.initialize();
|
|
361
402
|
if (!this.searchProvider || !this.searchProvider.isReady()) {
|
|
362
403
|
return {
|
|
363
404
|
content: [{ type: "text", text: "Server not initialized. Please try again." }],
|
|
@@ -372,20 +413,24 @@ var McpDocsServer = class {
|
|
|
372
413
|
} catch (error) {
|
|
373
414
|
console.error("[MCP] Search error:", error);
|
|
374
415
|
return {
|
|
375
|
-
content: [
|
|
416
|
+
content: [
|
|
417
|
+
{
|
|
418
|
+
type: "text",
|
|
419
|
+
text: "An error occurred while searching. Please try again."
|
|
420
|
+
}
|
|
421
|
+
],
|
|
376
422
|
isError: true
|
|
377
423
|
};
|
|
378
424
|
}
|
|
379
425
|
}
|
|
380
426
|
);
|
|
381
|
-
|
|
427
|
+
server.registerTool(
|
|
382
428
|
docsFetchTool.name,
|
|
383
429
|
{
|
|
384
430
|
description: docsFetchTool.description,
|
|
385
431
|
inputSchema: docsFetchTool.inputSchema
|
|
386
432
|
},
|
|
387
433
|
async ({ url }) => {
|
|
388
|
-
await this.initialize();
|
|
389
434
|
if (!this.searchProvider || !this.searchProvider.isReady()) {
|
|
390
435
|
return {
|
|
391
436
|
content: [{ type: "text", text: "Server not initialized. Please try again." }],
|
|
@@ -400,7 +445,12 @@ var McpDocsServer = class {
|
|
|
400
445
|
} catch (error) {
|
|
401
446
|
console.error("[MCP] Fetch error:", error);
|
|
402
447
|
return {
|
|
403
|
-
content: [
|
|
448
|
+
content: [
|
|
449
|
+
{
|
|
450
|
+
type: "text",
|
|
451
|
+
text: "An error occurred while fetching the page. Please try again."
|
|
452
|
+
}
|
|
453
|
+
],
|
|
404
454
|
isError: true
|
|
405
455
|
};
|
|
406
456
|
}
|
|
@@ -424,43 +474,54 @@ var McpDocsServer = class {
|
|
|
424
474
|
*
|
|
425
475
|
* For file-based config: reads from disk
|
|
426
476
|
* For data config: uses pre-loaded data directly
|
|
477
|
+
*
|
|
478
|
+
* Uses promise-based locking to prevent concurrent initialization.
|
|
479
|
+
* Caches initialization errors so repeated calls fail fast.
|
|
427
480
|
*/
|
|
428
481
|
async initialize() {
|
|
482
|
+
if (this.initError) {
|
|
483
|
+
throw this.initError;
|
|
484
|
+
}
|
|
429
485
|
if (this.initialized) {
|
|
430
486
|
return;
|
|
431
487
|
}
|
|
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;
|
|
488
|
+
if (!this.initPromise) {
|
|
489
|
+
this.initPromise = this._doInitialize().catch((error) => {
|
|
490
|
+
this.initError = error instanceof Error ? error : new Error(String(error));
|
|
491
|
+
this.initPromise = null;
|
|
492
|
+
throw this.initError;
|
|
493
|
+
});
|
|
457
494
|
}
|
|
495
|
+
return this.initPromise;
|
|
496
|
+
}
|
|
497
|
+
async _doInitialize() {
|
|
498
|
+
const searchSpecifier = this.config.search ?? "flexsearch";
|
|
499
|
+
this.searchProvider = await loadSearchProvider(searchSpecifier);
|
|
500
|
+
const providerContext = {
|
|
501
|
+
baseUrl: this.config.baseUrl ?? "",
|
|
502
|
+
serverName: this.config.name,
|
|
503
|
+
serverVersion: this.config.version ?? "1.0.0",
|
|
504
|
+
outputDir: ""
|
|
505
|
+
// Not relevant for runtime
|
|
506
|
+
};
|
|
507
|
+
const initData = {};
|
|
508
|
+
if (isDataConfig(this.config)) {
|
|
509
|
+
initData.docs = this.config.docs;
|
|
510
|
+
initData.indexData = this.config.searchIndexData;
|
|
511
|
+
} else if (isFileConfig(this.config)) {
|
|
512
|
+
initData.docsPath = this.config.docsPath;
|
|
513
|
+
initData.indexPath = this.config.indexPath;
|
|
514
|
+
} else {
|
|
515
|
+
throw new Error("Invalid server config: must provide either file paths or pre-loaded data");
|
|
516
|
+
}
|
|
517
|
+
await this.searchProvider.initialize(providerContext, initData);
|
|
518
|
+
this.initialized = true;
|
|
458
519
|
}
|
|
459
520
|
/**
|
|
460
521
|
* Handle an HTTP request using the MCP SDK's transport
|
|
461
522
|
*
|
|
462
523
|
* This method is designed for serverless environments (Vercel, Netlify).
|
|
463
|
-
*
|
|
524
|
+
* Creates a fresh McpServer per request to avoid concurrency issues.
|
|
464
525
|
*
|
|
465
526
|
* @param req - Node.js IncomingMessage or compatible request object
|
|
466
527
|
* @param res - Node.js ServerResponse or compatible response object
|
|
@@ -468,13 +529,14 @@ var McpDocsServer = class {
|
|
|
468
529
|
*/
|
|
469
530
|
async handleHttpRequest(req, res, parsedBody) {
|
|
470
531
|
await this.initialize();
|
|
532
|
+
const server = this.createMcpServer();
|
|
471
533
|
const transport = new StreamableHTTPServerTransport({
|
|
472
534
|
sessionIdGenerator: void 0,
|
|
473
535
|
// Stateless mode - no session tracking
|
|
474
536
|
enableJsonResponse: true
|
|
475
537
|
// Return JSON instead of SSE streams
|
|
476
538
|
});
|
|
477
|
-
await
|
|
539
|
+
await server.connect(transport);
|
|
478
540
|
try {
|
|
479
541
|
await transport.handleRequest(req, res, parsedBody);
|
|
480
542
|
} finally {
|
|
@@ -486,18 +548,20 @@ var McpDocsServer = class {
|
|
|
486
548
|
*
|
|
487
549
|
* This method is designed for Web Standard environments that use
|
|
488
550
|
* the Fetch API Request/Response pattern.
|
|
551
|
+
* Creates a fresh McpServer per request to avoid concurrency issues.
|
|
489
552
|
*
|
|
490
553
|
* @param request - Web Standard Request object
|
|
491
554
|
* @returns Web Standard Response object
|
|
492
555
|
*/
|
|
493
556
|
async handleWebRequest(request) {
|
|
494
557
|
await this.initialize();
|
|
558
|
+
const server = this.createMcpServer();
|
|
495
559
|
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
496
560
|
sessionIdGenerator: void 0,
|
|
497
561
|
// Stateless mode
|
|
498
562
|
enableJsonResponse: true
|
|
499
563
|
});
|
|
500
|
-
await
|
|
564
|
+
await server.connect(transport);
|
|
501
565
|
try {
|
|
502
566
|
return await transport.handleRequest(request);
|
|
503
567
|
} finally {
|
|
@@ -511,9 +575,8 @@ var McpDocsServer = class {
|
|
|
511
575
|
*/
|
|
512
576
|
async getStatus() {
|
|
513
577
|
let docCount = 0;
|
|
514
|
-
if (this.searchProvider
|
|
515
|
-
|
|
516
|
-
docCount = docs ? Object.keys(docs).length : 0;
|
|
578
|
+
if (this.searchProvider?.getDocCount) {
|
|
579
|
+
docCount = this.searchProvider.getDocCount();
|
|
517
580
|
}
|
|
518
581
|
return {
|
|
519
582
|
name: this.config.name,
|
|
@@ -524,37 +587,29 @@ var McpDocsServer = class {
|
|
|
524
587
|
searchProvider: this.searchProvider?.name
|
|
525
588
|
};
|
|
526
589
|
}
|
|
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
590
|
};
|
|
536
591
|
|
|
537
592
|
// src/adapters/cors.ts
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
return { ...CORS_HEADERS };
|
|
593
|
+
function getCorsHeaders(origin = "*") {
|
|
594
|
+
return {
|
|
595
|
+
"Access-Control-Allow-Origin": origin,
|
|
596
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
597
|
+
"Access-Control-Allow-Headers": "Content-Type"
|
|
598
|
+
};
|
|
545
599
|
}
|
|
546
600
|
|
|
547
601
|
// src/adapters/vercel.ts
|
|
548
602
|
function createVercelHandler(config) {
|
|
549
603
|
let server = null;
|
|
604
|
+
const { corsOrigin, ...serverConfig } = config;
|
|
550
605
|
function getServer() {
|
|
551
606
|
if (!server) {
|
|
552
|
-
server = new McpDocsServer(
|
|
607
|
+
server = new McpDocsServer(serverConfig);
|
|
553
608
|
}
|
|
554
609
|
return server;
|
|
555
610
|
}
|
|
556
611
|
return async function handler(req, res) {
|
|
557
|
-
const corsHeaders = getCorsHeaders();
|
|
612
|
+
const corsHeaders = getCorsHeaders(corsOrigin);
|
|
558
613
|
if (req.method === "OPTIONS") {
|
|
559
614
|
res.writeHead(204, corsHeaders);
|
|
560
615
|
res.end();
|
|
@@ -588,14 +643,13 @@ function createVercelHandler(config) {
|
|
|
588
643
|
const mcpServer = getServer();
|
|
589
644
|
await mcpServer.handleHttpRequest(req, res, req.body);
|
|
590
645
|
} catch (error) {
|
|
591
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
592
646
|
console.error("MCP Server Error:", error);
|
|
593
647
|
return res.status(500).json({
|
|
594
648
|
jsonrpc: "2.0",
|
|
595
649
|
id: null,
|
|
596
650
|
error: {
|
|
597
651
|
code: -32603,
|
|
598
|
-
message:
|
|
652
|
+
message: "Internal server error"
|
|
599
653
|
}
|
|
600
654
|
});
|
|
601
655
|
}
|
|
@@ -635,14 +689,15 @@ async function responseToNetlify(response, additionalHeaders) {
|
|
|
635
689
|
}
|
|
636
690
|
function createNetlifyHandler(config) {
|
|
637
691
|
let server = null;
|
|
692
|
+
const { corsOrigin, ...serverConfig } = config;
|
|
638
693
|
function getServer() {
|
|
639
694
|
if (!server) {
|
|
640
|
-
server = new McpDocsServer(
|
|
695
|
+
server = new McpDocsServer(serverConfig);
|
|
641
696
|
}
|
|
642
697
|
return server;
|
|
643
698
|
}
|
|
644
699
|
return async function handler(event, _context) {
|
|
645
|
-
const corsHeaders = getCorsHeaders();
|
|
700
|
+
const corsHeaders = getCorsHeaders(corsOrigin);
|
|
646
701
|
const headers = {
|
|
647
702
|
"Content-Type": "application/json",
|
|
648
703
|
...corsHeaders
|
|
@@ -682,7 +737,6 @@ function createNetlifyHandler(config) {
|
|
|
682
737
|
const response = await mcpServer.handleWebRequest(request);
|
|
683
738
|
return await responseToNetlify(response, corsHeaders);
|
|
684
739
|
} catch (error) {
|
|
685
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
686
740
|
console.error("MCP Server Error:", error);
|
|
687
741
|
return {
|
|
688
742
|
statusCode: 500,
|
|
@@ -692,7 +746,7 @@ function createNetlifyHandler(config) {
|
|
|
692
746
|
id: null,
|
|
693
747
|
error: {
|
|
694
748
|
code: -32603,
|
|
695
|
-
message:
|
|
749
|
+
message: "Internal server error"
|
|
696
750
|
}
|
|
697
751
|
})
|
|
698
752
|
};
|
|
@@ -703,12 +757,13 @@ function createNetlifyHandler(config) {
|
|
|
703
757
|
// src/adapters/cloudflare.ts
|
|
704
758
|
function createCloudflareHandler(config) {
|
|
705
759
|
let server = null;
|
|
760
|
+
const { corsOrigin, ...rest } = config;
|
|
706
761
|
const serverConfig = {
|
|
707
|
-
docs:
|
|
708
|
-
searchIndexData:
|
|
709
|
-
name:
|
|
710
|
-
version:
|
|
711
|
-
baseUrl:
|
|
762
|
+
docs: rest.docs,
|
|
763
|
+
searchIndexData: rest.searchIndexData,
|
|
764
|
+
name: rest.name,
|
|
765
|
+
version: rest.version,
|
|
766
|
+
baseUrl: rest.baseUrl
|
|
712
767
|
};
|
|
713
768
|
function getServer() {
|
|
714
769
|
if (!server) {
|
|
@@ -717,7 +772,7 @@ function createCloudflareHandler(config) {
|
|
|
717
772
|
return server;
|
|
718
773
|
}
|
|
719
774
|
return async function fetch(request) {
|
|
720
|
-
const corsHeaders = getCorsHeaders();
|
|
775
|
+
const corsHeaders = getCorsHeaders(corsOrigin);
|
|
721
776
|
if (request.method === "OPTIONS") {
|
|
722
777
|
return new Response(null, { status: 204, headers: corsHeaders });
|
|
723
778
|
}
|
|
@@ -758,7 +813,6 @@ function createCloudflareHandler(config) {
|
|
|
758
813
|
headers: newHeaders
|
|
759
814
|
});
|
|
760
815
|
} catch (error) {
|
|
761
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
762
816
|
console.error("MCP Server Error:", error);
|
|
763
817
|
return new Response(
|
|
764
818
|
JSON.stringify({
|
|
@@ -766,7 +820,7 @@ function createCloudflareHandler(config) {
|
|
|
766
820
|
id: null,
|
|
767
821
|
error: {
|
|
768
822
|
code: -32603,
|
|
769
|
-
message:
|
|
823
|
+
message: "Internal server error"
|
|
770
824
|
}
|
|
771
825
|
}),
|
|
772
826
|
{
|
|
@@ -891,31 +945,21 @@ function generateCloudflareFiles(name, baseUrl) {
|
|
|
891
945
|
content: `/**
|
|
892
946
|
* Cloudflare Worker for MCP server
|
|
893
947
|
*
|
|
894
|
-
*
|
|
895
|
-
*
|
|
896
|
-
*
|
|
897
|
-
* For bundling, use wrangler with custom build configuration.
|
|
948
|
+
* Workers cannot access the filesystem, so docs and search index
|
|
949
|
+
* are imported as JSON modules and passed as pre-loaded data.
|
|
898
950
|
*/
|
|
899
951
|
|
|
900
952
|
import { createCloudflareHandler } from 'docusaurus-plugin-mcp-server/adapters';
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
// import docs from '../build/mcp/docs.json';
|
|
904
|
-
// import searchIndex from '../build/mcp/search-index.json';
|
|
905
|
-
|
|
906
|
-
// Option 2: Use KV bindings (requires KV namespace configuration)
|
|
907
|
-
// const docs = await env.MCP_KV.get('docs', { type: 'json' });
|
|
908
|
-
// const searchIndex = await env.MCP_KV.get('search-index', { type: 'json' });
|
|
953
|
+
import docs from '../build/mcp/docs.json';
|
|
954
|
+
import searchIndex from '../build/mcp/search-index.json';
|
|
909
955
|
|
|
910
956
|
export default {
|
|
911
957
|
fetch: createCloudflareHandler({
|
|
958
|
+
docs,
|
|
959
|
+
searchIndexData: searchIndex,
|
|
912
960
|
name: '${name}',
|
|
913
961
|
version: '1.0.0',
|
|
914
962
|
baseUrl: '${baseUrl}',
|
|
915
|
-
// docsPath and indexPath are used for file-based loading
|
|
916
|
-
// For Workers, you'll need to configure data loading differently
|
|
917
|
-
docsPath: './mcp/docs.json',
|
|
918
|
-
indexPath: './mcp/search-index.json',
|
|
919
963
|
}),
|
|
920
964
|
};
|
|
921
965
|
`
|
|
@@ -927,14 +971,10 @@ export default {
|
|
|
927
971
|
main = "workers/mcp.js"
|
|
928
972
|
compatibility_date = "2024-01-01"
|
|
929
973
|
|
|
930
|
-
#
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
# Static assets (the Docusaurus build)
|
|
936
|
-
# [site]
|
|
937
|
-
# bucket = "./build"
|
|
974
|
+
# Allow importing JSON files as modules
|
|
975
|
+
[[rules]]
|
|
976
|
+
type = "Data"
|
|
977
|
+
globs = ["**/*.json"]
|
|
938
978
|
`
|
|
939
979
|
}
|
|
940
980
|
];
|
|
@@ -994,8 +1034,21 @@ function createNodeHandler(options) {
|
|
|
994
1034
|
const mcpServer = getServer();
|
|
995
1035
|
await mcpServer.handleHttpRequest(req, res, body);
|
|
996
1036
|
} catch (error) {
|
|
997
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
998
1037
|
console.error("[MCP] Request error:", error);
|
|
1038
|
+
if (error instanceof Error && error.message === "Request body too large") {
|
|
1039
|
+
res.writeHead(413, { "Content-Type": "application/json" });
|
|
1040
|
+
res.end(
|
|
1041
|
+
JSON.stringify({
|
|
1042
|
+
jsonrpc: "2.0",
|
|
1043
|
+
id: null,
|
|
1044
|
+
error: {
|
|
1045
|
+
code: -32600,
|
|
1046
|
+
message: "Request body too large"
|
|
1047
|
+
}
|
|
1048
|
+
})
|
|
1049
|
+
);
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
999
1052
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1000
1053
|
res.end(
|
|
1001
1054
|
JSON.stringify({
|
|
@@ -1003,7 +1056,7 @@ function createNodeHandler(options) {
|
|
|
1003
1056
|
id: null,
|
|
1004
1057
|
error: {
|
|
1005
1058
|
code: -32603,
|
|
1006
|
-
message:
|
|
1059
|
+
message: "Internal server error"
|
|
1007
1060
|
}
|
|
1008
1061
|
})
|
|
1009
1062
|
);
|
|
@@ -1014,11 +1067,19 @@ function createNodeServer(options) {
|
|
|
1014
1067
|
const handler = createNodeHandler(options);
|
|
1015
1068
|
return createServer(handler);
|
|
1016
1069
|
}
|
|
1070
|
+
var MAX_BODY_SIZE = 1024 * 1024;
|
|
1017
1071
|
async function parseRequestBody(req) {
|
|
1018
1072
|
return new Promise((resolve, reject) => {
|
|
1019
1073
|
let body = "";
|
|
1074
|
+
let size = 0;
|
|
1020
1075
|
req.on("data", (chunk) => {
|
|
1021
|
-
|
|
1076
|
+
size += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.length;
|
|
1077
|
+
if (size > MAX_BODY_SIZE) {
|
|
1078
|
+
req.destroy();
|
|
1079
|
+
reject(new Error("Request body too large"));
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
body += chunk.toString();
|
|
1022
1083
|
});
|
|
1023
1084
|
req.on("end", () => {
|
|
1024
1085
|
try {
|