@farming-labs/docs 0.2.52 → 0.2.54
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/agent-BXvi0uhS.mjs +623 -0
- package/dist/{agent-CaOBIVFy.mjs → agent-DXkXi-pS.mjs} +765 -22
- package/dist/agent-evals-BD17jOqL.mjs +1166 -0
- package/dist/agent-export-n-a0GbeU.mjs +854 -0
- package/dist/agent-scope-B8lptqCd.mjs +283 -0
- package/dist/agent-surface-drift-LS8zQgbq.mjs +1429 -0
- package/dist/{agents-DjhjZaNP.mjs → agents-BJaRQimo.mjs} +7 -5
- package/dist/{analytics-BZEwTK-8.mjs → analytics-Bx44lg6d.mjs} +1 -1
- package/dist/cli/index.mjs +43 -28
- package/dist/client/react.d.mts +1 -1
- package/dist/client/react.mjs +1 -1
- package/dist/{cloud-C_Ok9rC5.mjs → cloud-HWFlbfLJ.mjs} +4 -4
- package/dist/{cloud-ask-ai-Cl-xaV8j.mjs → cloud-ask-ai-1k0q6OAH.mjs} +1 -1
- package/dist/{cloud-ask-ai-DcMe6xOf.d.mts → cloud-ask-ai-466g6uAi.d.mts} +1 -1
- package/dist/code-blocks-C9awWzEQ.mjs +871 -0
- package/dist/codeblocks-BzfkREEC.mjs +250 -0
- package/dist/{config-tiQCZ46q.mjs → config-CydaZ5PB.mjs} +52 -11
- package/dist/{dev-Tm9Ss4D6.mjs → dev-BA2kRuCn.mjs} +3 -3
- package/dist/docs-cloud-server.d.mts +2 -2
- package/dist/docs-cloud-server.mjs +2 -2
- package/dist/{doctor-O9o9kjN8.mjs → doctor-CQpQ2zZm.mjs} +177 -47
- package/dist/{downgrade-CYEaOctn.mjs → downgrade-BStNhyCc.mjs} +2 -2
- package/dist/i18n-CAlj1ADU.mjs +40 -0
- package/dist/index.d.mts +158 -3
- package/dist/index.mjs +7 -6
- package/dist/{init-Ch5On0zm.mjs → init-gnHQ_Pz9.mjs} +3 -3
- package/dist/{mcp-DuCPNHS-.mjs → mcp-DnxZZWpY.mjs} +10 -5
- package/dist/mcp.d.mts +122 -6
- package/dist/mcp.mjs +1943 -86
- package/dist/{package-version-C8PigBZk.mjs → package-version-DRIc08EU.mjs} +1 -1
- package/dist/{reading-time-CPAy1SWO.mjs → reading-time-BrTd3DIh.mjs} +277 -33
- package/dist/{review-lm3dt7yy.mjs → review-B6gyEFkD.mjs} +184 -14
- package/dist/{robots-DV7u4Ire.mjs → robots-4BUJxlvV.mjs} +4 -4
- package/dist/{robots-XVPs9lVz.mjs → robots-DAptQnkx.mjs} +1 -1
- package/dist/{search-BWuU70B0.mjs → search-CJIC1Cqo.mjs} +173 -79
- package/dist/{search-B6nEkx86.mjs → search-CbPm2x5g.mjs} +6 -4
- package/dist/{search-DFEhf9-s.d.mts → search-CfWvmVYA.d.mts} +3 -1
- package/dist/server.d.mts +108 -5
- package/dist/server.mjs +9 -8
- package/dist/{sitemap-BEiKy4Iy.mjs → sitemap-D6nP3J1Q.mjs} +8 -6
- package/dist/{sitemap-server-xEHtDUDB.mjs → sitemap-server-wsNLyVkb.mjs} +1 -1
- package/dist/{templates-uaauJcTO.mjs → templates-1Cod8KrJ.mjs} +11 -5
- package/dist/{types-XqGLsmOD.d.mts → types-BVgucdVm.d.mts} +194 -2
- package/dist/{upgrade-DzQtpgJM.mjs → upgrade-CXYRNV0C.mjs} +2 -2
- package/package.json +1 -1
- package/dist/agent-DXDOMJBw.mjs +0 -9
- package/dist/codeblocks-z9iT794h.mjs +0 -1713
- package/dist/review-B7goPYUb.mjs +0 -541
- /package/dist/{cloud-analytics-Dlk_byos.mjs → cloud-analytics-CSyFE6SS.mjs} +0 -0
- /package/dist/{errors-CW1LnxaQ.mjs → errors-BFUtdZfC.mjs} +0 -0
- /package/dist/{utils-D-xTRNuh.mjs → utils-DBCCkkJS.mjs} +0 -0
package/dist/mcp.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { a as emitDocsAnalyticsEvent, i as emitDocsAgentTraceEvent, n as createDocsAgentTraceContext, r as createDocsAgentTraceId } from "./analytics-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { a as emitDocsAnalyticsEvent, i as emitDocsAgentTraceEvent, n as createDocsAgentTraceContext, r as createDocsAgentTraceId } from "./analytics-Bx44lg6d.mjs";
|
|
2
|
+
import { Gt as hasStructuredPageAgentContract, Kt as normalizePageAgentFrontmatter, Lt as normalizeDocsRelated, Rt as renderDocsRelatedMarkdownLines, Xt as upsertPageAgentContractMarkdown } from "./agent-DXkXi-pS.mjs";
|
|
3
|
+
import { C as resolvePageSidebarFolderIndexBehavior, E as emitDocsTelemetryAgentSurfaceEvent, O as emitDocsTelemetryMcpToolEvent, k as emitDocsTelemetryProjectEvent, m as parseDocsMarkdownSections, p as findDocsMarkdownSection, u as performDocsSearch, x as stripGeneratedAgentProvenance } from "./search-CJIC1Cqo.mjs";
|
|
4
|
+
import { a as normalizeAgentScopeValues, i as normalizeAgentLocale, n as agentVersionConstraintsOverlap, r as normalizeAgentFramework, t as agentVersionConstraintMatches } from "./agent-scope-B8lptqCd.mjs";
|
|
4
5
|
import matter from "gray-matter";
|
|
5
6
|
import fs from "node:fs";
|
|
6
7
|
import path from "node:path";
|
|
@@ -14,7 +15,41 @@ import * as z from "zod/v4";
|
|
|
14
15
|
const DEFAULT_MCP_ROUTE = "/api/docs/mcp";
|
|
15
16
|
const DEFAULT_MCP_VERSION = "0.0.0";
|
|
16
17
|
const DEFAULT_MCP_NAME = "@farming-labs/docs";
|
|
17
|
-
const
|
|
18
|
+
const DEFAULT_MCP_CONTEXT_TOKEN_BUDGET = 4e3;
|
|
19
|
+
const MIN_MCP_CONTEXT_TOKEN_BUDGET = 256;
|
|
20
|
+
const MAX_MCP_CONTEXT_TOKEN_BUDGET = 32e3;
|
|
21
|
+
const UTF8_ENCODER = new TextEncoder();
|
|
22
|
+
const DEFAULT_DOCS_MCP_MAX_BODY_BYTES = 1024 * 1024;
|
|
23
|
+
const DEFAULT_DOCS_MCP_CORS_MAX_AGE_SECONDS = 600;
|
|
24
|
+
const DEFAULT_DOCS_MCP_CORS_ALLOWED_HEADERS = Object.freeze([
|
|
25
|
+
"Accept",
|
|
26
|
+
"Authorization",
|
|
27
|
+
"Content-Type",
|
|
28
|
+
"Last-Event-ID",
|
|
29
|
+
"MCP-Protocol-Version",
|
|
30
|
+
"MCP-Session-Id"
|
|
31
|
+
]);
|
|
32
|
+
const DEFAULT_DOCS_MCP_CORS_EXPOSED_HEADERS = Object.freeze([
|
|
33
|
+
"MCP-Protocol-Version",
|
|
34
|
+
"MCP-Session-Id",
|
|
35
|
+
"WWW-Authenticate"
|
|
36
|
+
]);
|
|
37
|
+
function freezeDocsConfigSchemaOptions(options) {
|
|
38
|
+
for (const option of options) {
|
|
39
|
+
if (option.values) Object.freeze(option.values);
|
|
40
|
+
if (option.children) {
|
|
41
|
+
freezeDocsConfigSchemaOptions([...option.children]);
|
|
42
|
+
Object.freeze(option.children);
|
|
43
|
+
}
|
|
44
|
+
Object.freeze(option);
|
|
45
|
+
}
|
|
46
|
+
return Object.freeze(options);
|
|
47
|
+
}
|
|
48
|
+
function freezeDocsConfigSchemaExamples(examples) {
|
|
49
|
+
for (const example of examples) Object.freeze(example);
|
|
50
|
+
return Object.freeze(examples);
|
|
51
|
+
}
|
|
52
|
+
const DOCS_CONFIG_SCHEMA_OPTIONS_TEMPLATE = [
|
|
18
53
|
{
|
|
19
54
|
path: "entry",
|
|
20
55
|
name: "entry",
|
|
@@ -23,6 +58,14 @@ const DOCS_CONFIG_SCHEMA_OPTIONS = [
|
|
|
23
58
|
description: "URL path prefix for documentation routes, for example \"docs\" creates /docs.",
|
|
24
59
|
docs: "/docs/overview"
|
|
25
60
|
},
|
|
61
|
+
{
|
|
62
|
+
path: "docsPath",
|
|
63
|
+
name: "docsPath",
|
|
64
|
+
type: "string",
|
|
65
|
+
default: "same as entry",
|
|
66
|
+
description: "Public route prefix for docs pages when it differs from the source entry directory.",
|
|
67
|
+
docs: "/docs/overview"
|
|
68
|
+
},
|
|
26
69
|
{
|
|
27
70
|
path: "contentDir",
|
|
28
71
|
name: "contentDir",
|
|
@@ -31,6 +74,13 @@ const DOCS_CONFIG_SCHEMA_OPTIONS = [
|
|
|
31
74
|
description: "Path to markdown content files. Adapters outside Next.js usually need this when content does not live under the route prefix.",
|
|
32
75
|
docs: "/docs/overview"
|
|
33
76
|
},
|
|
77
|
+
{
|
|
78
|
+
path: "i18n",
|
|
79
|
+
name: "i18n",
|
|
80
|
+
type: "DocsI18nConfig",
|
|
81
|
+
description: "Locale discovery, default locale, and localized docs content configuration.",
|
|
82
|
+
docs: "/docs/reference"
|
|
83
|
+
},
|
|
34
84
|
{
|
|
35
85
|
path: "staticExport",
|
|
36
86
|
name: "staticExport",
|
|
@@ -46,6 +96,25 @@ const DOCS_CONFIG_SCHEMA_OPTIONS = [
|
|
|
46
96
|
description: "Theme instance from a theme factory such as fumadocs() or pixelBorder().",
|
|
47
97
|
docs: "/docs/customization/themes"
|
|
48
98
|
},
|
|
99
|
+
{
|
|
100
|
+
path: "analytics",
|
|
101
|
+
name: "analytics",
|
|
102
|
+
type: "boolean | DocsAnalyticsConfig",
|
|
103
|
+
default: false,
|
|
104
|
+
description: "Built-in privacy-aware product and agent surface analytics."
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
path: "telemetry",
|
|
108
|
+
name: "telemetry",
|
|
109
|
+
type: "boolean | DocsTelemetryConfig",
|
|
110
|
+
description: "Project telemetry controls for framework and agent-surface events."
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
path: "observability",
|
|
114
|
+
name: "observability",
|
|
115
|
+
type: "boolean | DocsObservabilityConfig",
|
|
116
|
+
description: "Tracing and observability callbacks for search, AI, and agent operations."
|
|
117
|
+
},
|
|
49
118
|
{
|
|
50
119
|
path: "nav",
|
|
51
120
|
name: "nav",
|
|
@@ -135,12 +204,419 @@ const DOCS_CONFIG_SCHEMA_OPTIONS = [
|
|
|
135
204
|
default: false,
|
|
136
205
|
description: "Opt-in estimated reading time label with per-page overrides and label format."
|
|
137
206
|
},
|
|
207
|
+
{
|
|
208
|
+
path: "lastUpdated",
|
|
209
|
+
name: "lastUpdated",
|
|
210
|
+
type: "boolean | LastUpdatedConfig",
|
|
211
|
+
description: "Last-updated metadata and labels derived from source history or page data."
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
path: "ordering",
|
|
215
|
+
name: "ordering",
|
|
216
|
+
type: "\"alphabetical\" | \"numeric\" | OrderingItem[]",
|
|
217
|
+
description: "Navigation ordering strategy or explicit ordered navigation entries."
|
|
218
|
+
},
|
|
138
219
|
{
|
|
139
220
|
path: "agent",
|
|
140
221
|
name: "agent",
|
|
141
222
|
type: "DocsAgentConfig",
|
|
142
|
-
description: "
|
|
143
|
-
docs: "/docs/getting-started/agent-ready-docs"
|
|
223
|
+
description: "Agent compaction defaults and deterministic usefulness evaluations.",
|
|
224
|
+
docs: "/docs/getting-started/agent-ready-docs",
|
|
225
|
+
children: [{
|
|
226
|
+
path: "agent.compact",
|
|
227
|
+
name: "compact",
|
|
228
|
+
type: "DocsAgentCompactConfig",
|
|
229
|
+
description: "Defaults for generated agent.md compaction.",
|
|
230
|
+
children: [
|
|
231
|
+
{
|
|
232
|
+
path: "agent.compact.apiKey",
|
|
233
|
+
name: "apiKey",
|
|
234
|
+
type: "string",
|
|
235
|
+
description: "Direct compaction provider API key; prefer apiKeyEnv."
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
path: "agent.compact.apiKeyEnv",
|
|
239
|
+
name: "apiKeyEnv",
|
|
240
|
+
type: "string",
|
|
241
|
+
description: "Environment variable containing the compaction provider API key."
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
path: "agent.compact.baseUrl",
|
|
245
|
+
name: "baseUrl",
|
|
246
|
+
type: "string",
|
|
247
|
+
description: "Compaction provider base URL."
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
path: "agent.compact.model",
|
|
251
|
+
name: "model",
|
|
252
|
+
type: "string",
|
|
253
|
+
description: "Compaction model identifier."
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
path: "agent.compact.aggressiveness",
|
|
257
|
+
name: "aggressiveness",
|
|
258
|
+
type: "number",
|
|
259
|
+
default: .3,
|
|
260
|
+
description: "Compression aggressiveness from 0 to 1."
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
path: "agent.compact.maxOutputTokens",
|
|
264
|
+
name: "maxOutputTokens",
|
|
265
|
+
type: "number",
|
|
266
|
+
description: "Upper output token target."
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
path: "agent.compact.minOutputTokens",
|
|
270
|
+
name: "minOutputTokens",
|
|
271
|
+
type: "number",
|
|
272
|
+
description: "Lower output token target."
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
path: "agent.compact.protectJson",
|
|
276
|
+
name: "protectJson",
|
|
277
|
+
type: "boolean",
|
|
278
|
+
description: "Preserve JSON objects during compaction when supported."
|
|
279
|
+
}
|
|
280
|
+
]
|
|
281
|
+
}, {
|
|
282
|
+
path: "agent.evaluations",
|
|
283
|
+
name: "evaluations",
|
|
284
|
+
type: "boolean | DocsAgentEvaluationsConfig",
|
|
285
|
+
description: "Deterministic golden tasks for retrieval, citation, version, example, and budget evaluation.",
|
|
286
|
+
children: [
|
|
287
|
+
{
|
|
288
|
+
path: "agent.evaluations.enabled",
|
|
289
|
+
name: "enabled",
|
|
290
|
+
type: "boolean",
|
|
291
|
+
default: true,
|
|
292
|
+
description: "Enable configured golden-task evaluation."
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
path: "agent.evaluations.tokenBudget",
|
|
296
|
+
name: "tokenBudget",
|
|
297
|
+
type: "number",
|
|
298
|
+
default: 4e3,
|
|
299
|
+
description: "Default hard UTF-8 context-byte ceiling for golden tasks."
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
path: "agent.evaluations.topK",
|
|
303
|
+
name: "topK",
|
|
304
|
+
type: "number",
|
|
305
|
+
default: 5,
|
|
306
|
+
description: "Default number of ranked search results evaluated per task."
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
path: "agent.evaluations.tasks",
|
|
310
|
+
name: "tasks",
|
|
311
|
+
type: "DocsAgentGoldenTask[]",
|
|
312
|
+
description: "Offline golden task fixtures evaluated by docs doctor and docs review.",
|
|
313
|
+
children: [
|
|
314
|
+
{
|
|
315
|
+
path: "agent.evaluations.tasks[]",
|
|
316
|
+
name: "task",
|
|
317
|
+
type: "DocsAgentGoldenTask",
|
|
318
|
+
description: "One golden task entry."
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
path: "agent.evaluations.tasks[].id",
|
|
322
|
+
name: "id",
|
|
323
|
+
type: "string",
|
|
324
|
+
description: "Stable task identifier shown in diagnostics."
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
path: "agent.evaluations.tasks[].query",
|
|
328
|
+
name: "query",
|
|
329
|
+
type: "string",
|
|
330
|
+
description: "User-shaped retrieval query."
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
path: "agent.evaluations.tasks[].tokenBudget",
|
|
334
|
+
name: "tokenBudget",
|
|
335
|
+
type: "number",
|
|
336
|
+
description: "Per-task UTF-8 context-byte ceiling override."
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
path: "agent.evaluations.tasks[].topK",
|
|
340
|
+
name: "topK",
|
|
341
|
+
type: "number",
|
|
342
|
+
description: "Per-task ranked retrieval depth override."
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
path: "agent.evaluations.tasks[].filters",
|
|
346
|
+
name: "filters",
|
|
347
|
+
type: "DocsAgentGoldenTaskFilters",
|
|
348
|
+
description: "Framework, version, and locale retrieval scope.",
|
|
349
|
+
children: [
|
|
350
|
+
{
|
|
351
|
+
path: "agent.evaluations.tasks[].filters.framework",
|
|
352
|
+
name: "framework",
|
|
353
|
+
type: "string",
|
|
354
|
+
description: "Required framework, such as nextjs or astro."
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
path: "agent.evaluations.tasks[].filters.version",
|
|
358
|
+
name: "version",
|
|
359
|
+
type: "string",
|
|
360
|
+
description: "Exact version requested by the task."
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
path: "agent.evaluations.tasks[].filters.locale",
|
|
364
|
+
name: "locale",
|
|
365
|
+
type: "string",
|
|
366
|
+
description: "Required locale."
|
|
367
|
+
}
|
|
368
|
+
]
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
path: "agent.evaluations.tasks[].expect",
|
|
372
|
+
name: "expect",
|
|
373
|
+
type: "DocsAgentGoldenTaskExpectation",
|
|
374
|
+
description: "Deterministic sources, rank, citation, example, and budget expectations.",
|
|
375
|
+
children: [
|
|
376
|
+
{
|
|
377
|
+
path: "agent.evaluations.tasks[].expect.relevantSources",
|
|
378
|
+
name: "relevantSources",
|
|
379
|
+
type: "string[]",
|
|
380
|
+
description: "Canonical page or section URLs that should answer the task."
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
path: "agent.evaluations.tasks[].expect.allowedSources",
|
|
384
|
+
name: "allowedSources",
|
|
385
|
+
type: "string[]",
|
|
386
|
+
description: "Additional legitimate citations that do not reduce precision."
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
path: "agent.evaluations.tasks[].expect.forbiddenSources",
|
|
390
|
+
name: "forbiddenSources",
|
|
391
|
+
type: "string[]",
|
|
392
|
+
description: "Sources that must not be retrieved or cited."
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
path: "agent.evaluations.tasks[].expect.requiredCitations",
|
|
396
|
+
name: "requiredCitations",
|
|
397
|
+
type: "string[]",
|
|
398
|
+
description: "Citations that must appear; defaults to relevantSources."
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
path: "agent.evaluations.tasks[].expect.minRecallAtK",
|
|
402
|
+
name: "minRecallAtK",
|
|
403
|
+
type: "number",
|
|
404
|
+
default: 1,
|
|
405
|
+
description: "Minimum relevant-source recall in the top K results."
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
path: "agent.evaluations.tasks[].expect.maxFirstRelevantRank",
|
|
409
|
+
name: "maxFirstRelevantRank",
|
|
410
|
+
type: "number",
|
|
411
|
+
description: "Maximum acceptable rank of the first relevant source."
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
path: "agent.evaluations.tasks[].expect.minUsefulByteRatio",
|
|
415
|
+
name: "minUsefulByteRatio",
|
|
416
|
+
type: "number",
|
|
417
|
+
description: "Minimum share of context bytes supplied by relevant sources."
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
path: "agent.evaluations.tasks[].expect.examples",
|
|
421
|
+
name: "examples",
|
|
422
|
+
type: "DocsAgentGoldenExpectedExample[]",
|
|
423
|
+
description: "Runnable examples that must be present in returned context.",
|
|
424
|
+
children: [
|
|
425
|
+
{
|
|
426
|
+
path: "agent.evaluations.tasks[].expect.examples[]",
|
|
427
|
+
name: "example",
|
|
428
|
+
type: "DocsAgentGoldenExpectedExample",
|
|
429
|
+
description: "One expected code example."
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
path: "agent.evaluations.tasks[].expect.examples[].source",
|
|
433
|
+
name: "source",
|
|
434
|
+
type: "string",
|
|
435
|
+
description: "Canonical source URL containing the example."
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
path: "agent.evaluations.tasks[].expect.examples[].language",
|
|
439
|
+
name: "language",
|
|
440
|
+
type: "string",
|
|
441
|
+
description: "Expected code-fence language."
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
path: "agent.evaluations.tasks[].expect.examples[].framework",
|
|
445
|
+
name: "framework",
|
|
446
|
+
type: "string",
|
|
447
|
+
description: "Expected framework metadata."
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
path: "agent.evaluations.tasks[].expect.examples[].packageManager",
|
|
451
|
+
name: "packageManager",
|
|
452
|
+
type: "string",
|
|
453
|
+
description: "Expected package-manager metadata."
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
path: "agent.evaluations.tasks[].expect.examples[].title",
|
|
457
|
+
name: "title",
|
|
458
|
+
type: "string",
|
|
459
|
+
description: "Expected code-fence title metadata."
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
path: "agent.evaluations.tasks[].expect.examples[].runnable",
|
|
463
|
+
name: "runnable",
|
|
464
|
+
type: "boolean",
|
|
465
|
+
default: true,
|
|
466
|
+
description: "Whether the example must be marked runnable."
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
path: "agent.evaluations.tasks[].expect.examples[].includes",
|
|
470
|
+
name: "includes",
|
|
471
|
+
type: "string[]",
|
|
472
|
+
description: "Literal code fragments that must appear."
|
|
473
|
+
}
|
|
474
|
+
]
|
|
475
|
+
}
|
|
476
|
+
]
|
|
477
|
+
}
|
|
478
|
+
]
|
|
479
|
+
}
|
|
480
|
+
]
|
|
481
|
+
}]
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
path: "review",
|
|
485
|
+
name: "review",
|
|
486
|
+
type: "boolean | DocsReviewConfig",
|
|
487
|
+
description: "Docs review scoring, CI behavior, and diagnostic rule severities.",
|
|
488
|
+
docs: "/docs/reference",
|
|
489
|
+
children: [
|
|
490
|
+
{
|
|
491
|
+
path: "review.enabled",
|
|
492
|
+
name: "enabled",
|
|
493
|
+
type: "boolean",
|
|
494
|
+
default: true,
|
|
495
|
+
description: "Enable Docs Review."
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
path: "review.score",
|
|
499
|
+
name: "score",
|
|
500
|
+
type: "DocsReviewScoreConfig",
|
|
501
|
+
description: "Healthy threshold and finding severity weights.",
|
|
502
|
+
children: [{
|
|
503
|
+
path: "review.score.threshold",
|
|
504
|
+
name: "threshold",
|
|
505
|
+
type: "number",
|
|
506
|
+
default: 80,
|
|
507
|
+
description: "Minimum healthy review score."
|
|
508
|
+
}, {
|
|
509
|
+
path: "review.score.weights",
|
|
510
|
+
name: "weights",
|
|
511
|
+
type: "{ error?: number; warn?: number; suggestion?: number }",
|
|
512
|
+
description: "Point deductions for each finding severity.",
|
|
513
|
+
children: [
|
|
514
|
+
{
|
|
515
|
+
path: "review.score.weights.error",
|
|
516
|
+
name: "error",
|
|
517
|
+
type: "number",
|
|
518
|
+
default: 20,
|
|
519
|
+
description: "Point deduction for an error finding."
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
path: "review.score.weights.warn",
|
|
523
|
+
name: "warn",
|
|
524
|
+
type: "number",
|
|
525
|
+
default: 8,
|
|
526
|
+
description: "Point deduction for a warning finding."
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
path: "review.score.weights.suggestion",
|
|
530
|
+
name: "suggestion",
|
|
531
|
+
type: "number",
|
|
532
|
+
default: 2,
|
|
533
|
+
description: "Point deduction for a suggestion finding."
|
|
534
|
+
}
|
|
535
|
+
]
|
|
536
|
+
}]
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
path: "review.ci",
|
|
540
|
+
name: "ci",
|
|
541
|
+
type: "boolean | DocsReviewCiConfig",
|
|
542
|
+
description: "GitHub Actions reporting and blocking behavior.",
|
|
543
|
+
children: [
|
|
544
|
+
{
|
|
545
|
+
path: "review.ci.enabled",
|
|
546
|
+
name: "enabled",
|
|
547
|
+
type: "boolean",
|
|
548
|
+
default: true,
|
|
549
|
+
description: "Enable review workflow generation."
|
|
550
|
+
},
|
|
551
|
+
{
|
|
552
|
+
path: "review.ci.name",
|
|
553
|
+
name: "name",
|
|
554
|
+
type: "string",
|
|
555
|
+
default: "docs-review",
|
|
556
|
+
description: "GitHub Actions job and check name."
|
|
557
|
+
},
|
|
558
|
+
{
|
|
559
|
+
path: "review.ci.mode",
|
|
560
|
+
name: "mode",
|
|
561
|
+
type: "\"off\" | \"warn\" | \"block\"",
|
|
562
|
+
default: "warn",
|
|
563
|
+
description: "Whether CI is disabled, advisory, or blocking.",
|
|
564
|
+
values: [
|
|
565
|
+
"off",
|
|
566
|
+
"warn",
|
|
567
|
+
"block"
|
|
568
|
+
]
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
path: "review.ci.annotations",
|
|
572
|
+
name: "annotations",
|
|
573
|
+
type: "boolean",
|
|
574
|
+
default: true,
|
|
575
|
+
description: "Emit GitHub workflow annotations."
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
path: "review.ci.comment",
|
|
579
|
+
name: "comment",
|
|
580
|
+
type: "boolean",
|
|
581
|
+
default: true,
|
|
582
|
+
description: "Allow the official action or bot to post PR comments."
|
|
583
|
+
}
|
|
584
|
+
]
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
path: "review.rules",
|
|
588
|
+
name: "rules",
|
|
589
|
+
type: "DocsReviewRulesConfig",
|
|
590
|
+
description: "Per-rule severity overrides.",
|
|
591
|
+
children: [
|
|
592
|
+
["brokenLinks", "error"],
|
|
593
|
+
["frontmatter", "error"],
|
|
594
|
+
["duplicateSlugs", "error"],
|
|
595
|
+
["invalidMdx", "error"],
|
|
596
|
+
["configExamples", "warn"],
|
|
597
|
+
["codeFenceMetadata", "warn"],
|
|
598
|
+
["runnableMetadata", "warn"],
|
|
599
|
+
["agentContext", "warn"],
|
|
600
|
+
["commandHealth", "warn"],
|
|
601
|
+
["relatedCoverage", "suggestion"],
|
|
602
|
+
["configConfidence", "warn"],
|
|
603
|
+
["agentSurfaceDrift", "error"],
|
|
604
|
+
["goldenTasks", "warn"]
|
|
605
|
+
].map(([name, defaultValue]) => ({
|
|
606
|
+
path: `review.rules.${name}`,
|
|
607
|
+
name,
|
|
608
|
+
type: "DocsReviewSeverity",
|
|
609
|
+
default: defaultValue,
|
|
610
|
+
description: `Severity override for the ${name} review rule.`,
|
|
611
|
+
values: [
|
|
612
|
+
"off",
|
|
613
|
+
"suggestion",
|
|
614
|
+
"warn",
|
|
615
|
+
"error"
|
|
616
|
+
]
|
|
617
|
+
}))
|
|
618
|
+
}
|
|
619
|
+
]
|
|
144
620
|
},
|
|
145
621
|
{
|
|
146
622
|
path: "pageActions",
|
|
@@ -328,6 +804,70 @@ const DOCS_CONFIG_SCHEMA_OPTIONS = [
|
|
|
328
804
|
default: "0.0.0",
|
|
329
805
|
description: "Version string reported to MCP clients."
|
|
330
806
|
},
|
|
807
|
+
{
|
|
808
|
+
path: "mcp.security",
|
|
809
|
+
name: "security",
|
|
810
|
+
type: "DocsMcpSecurityConfig",
|
|
811
|
+
description: "Streamable HTTP Origin validation, optional authentication, and request-size controls. The stdio transport is unaffected.",
|
|
812
|
+
children: [
|
|
813
|
+
{
|
|
814
|
+
path: "mcp.security.allowedOrigins",
|
|
815
|
+
name: "allowedOrigins",
|
|
816
|
+
type: "\"same-origin\" | string[] | callback",
|
|
817
|
+
default: "same-origin",
|
|
818
|
+
description: "Allow a supplied Origin header when it matches the MCP request origin, an explicit list, or a custom policy callback. Origin-less non-browser clients remain supported."
|
|
819
|
+
},
|
|
820
|
+
{
|
|
821
|
+
path: "mcp.security.authenticate",
|
|
822
|
+
name: "authenticate",
|
|
823
|
+
type: "DocsMcpAuthenticate",
|
|
824
|
+
default: "public (callback omitted)",
|
|
825
|
+
description: "Opt-in HTTP authentication callback. Return a principal to continue, null for 401, or a Response to control the rejection."
|
|
826
|
+
},
|
|
827
|
+
{
|
|
828
|
+
path: "mcp.security.maxBodyBytes",
|
|
829
|
+
name: "maxBodyBytes",
|
|
830
|
+
type: "number",
|
|
831
|
+
default: DEFAULT_DOCS_MCP_MAX_BODY_BYTES,
|
|
832
|
+
description: "Maximum accepted Streamable HTTP POST body size in bytes."
|
|
833
|
+
},
|
|
834
|
+
{
|
|
835
|
+
path: "mcp.security.cors",
|
|
836
|
+
name: "cors",
|
|
837
|
+
type: "boolean | DocsMcpCorsConfig",
|
|
838
|
+
default: true,
|
|
839
|
+
description: "Emit exact-Origin CORS responses for Origins accepted by allowedOrigins. Use an object for credentials, additional headers, and preflight cache controls.",
|
|
840
|
+
children: [
|
|
841
|
+
{
|
|
842
|
+
path: "mcp.security.cors.allowedHeaders",
|
|
843
|
+
name: "allowedHeaders",
|
|
844
|
+
type: "string[]",
|
|
845
|
+
description: "Additional request headers accepted during browser preflight."
|
|
846
|
+
},
|
|
847
|
+
{
|
|
848
|
+
path: "mcp.security.cors.exposedHeaders",
|
|
849
|
+
name: "exposedHeaders",
|
|
850
|
+
type: "string[]",
|
|
851
|
+
description: "Additional MCP response headers exposed to browser JavaScript."
|
|
852
|
+
},
|
|
853
|
+
{
|
|
854
|
+
path: "mcp.security.cors.allowCredentials",
|
|
855
|
+
name: "allowCredentials",
|
|
856
|
+
type: "boolean",
|
|
857
|
+
default: false,
|
|
858
|
+
description: "Allow credentialed browser requests using the validated exact Origin. Wildcard credentials are never emitted."
|
|
859
|
+
},
|
|
860
|
+
{
|
|
861
|
+
path: "mcp.security.cors.maxAgeSeconds",
|
|
862
|
+
name: "maxAgeSeconds",
|
|
863
|
+
type: "number",
|
|
864
|
+
default: DEFAULT_DOCS_MCP_CORS_MAX_AGE_SECONDS,
|
|
865
|
+
description: "Browser preflight cache lifetime in seconds."
|
|
866
|
+
}
|
|
867
|
+
]
|
|
868
|
+
}
|
|
869
|
+
]
|
|
870
|
+
},
|
|
331
871
|
{
|
|
332
872
|
path: "mcp.tools",
|
|
333
873
|
name: "tools",
|
|
@@ -349,6 +889,20 @@ const DOCS_CONFIG_SCHEMA_OPTIONS = [
|
|
|
349
889
|
default: true,
|
|
350
890
|
description: "Expose the list_pages tool."
|
|
351
891
|
},
|
|
892
|
+
{
|
|
893
|
+
path: "mcp.tools.listTasks",
|
|
894
|
+
name: "listTasks",
|
|
895
|
+
type: "boolean",
|
|
896
|
+
default: true,
|
|
897
|
+
description: "Expose the list_tasks tool."
|
|
898
|
+
},
|
|
899
|
+
{
|
|
900
|
+
path: "mcp.tools.readTask",
|
|
901
|
+
name: "readTask",
|
|
902
|
+
type: "boolean",
|
|
903
|
+
default: true,
|
|
904
|
+
description: "Expose the read_task tool."
|
|
905
|
+
},
|
|
352
906
|
{
|
|
353
907
|
path: "mcp.tools.getNavigation",
|
|
354
908
|
name: "getNavigation",
|
|
@@ -383,6 +937,13 @@ const DOCS_CONFIG_SCHEMA_OPTIONS = [
|
|
|
383
937
|
type: "boolean",
|
|
384
938
|
default: true,
|
|
385
939
|
description: "Expose the get_config_schema tool."
|
|
940
|
+
},
|
|
941
|
+
{
|
|
942
|
+
path: "mcp.tools.getContext",
|
|
943
|
+
name: "getContext",
|
|
944
|
+
type: "boolean",
|
|
945
|
+
default: true,
|
|
946
|
+
description: "Expose deterministic get_context retrieval with a conservative UTF-8 byte ceiling."
|
|
386
947
|
}
|
|
387
948
|
]
|
|
388
949
|
}
|
|
@@ -470,7 +1031,8 @@ const DOCS_CONFIG_SCHEMA_OPTIONS = [
|
|
|
470
1031
|
description: "Dynamic Open Graph image configuration."
|
|
471
1032
|
}
|
|
472
1033
|
];
|
|
473
|
-
const
|
|
1034
|
+
const DOCS_CONFIG_SCHEMA_OPTIONS = freezeDocsConfigSchemaOptions(DOCS_CONFIG_SCHEMA_OPTIONS_TEMPLATE);
|
|
1035
|
+
const DOCS_CONFIG_SCHEMA_EXAMPLES = freezeDocsConfigSchemaExamples([
|
|
474
1036
|
{
|
|
475
1037
|
title: "Minimal config",
|
|
476
1038
|
code: `import { defineDocs } from "@farming-labs/docs";
|
|
@@ -492,6 +1054,19 @@ export default defineDocs({
|
|
|
492
1054
|
getCodeExamples: true,
|
|
493
1055
|
},
|
|
494
1056
|
},
|
|
1057
|
+
});`
|
|
1058
|
+
},
|
|
1059
|
+
{
|
|
1060
|
+
title: "Opt-in MCP authentication",
|
|
1061
|
+
code: `export default defineDocs({
|
|
1062
|
+
mcp: {
|
|
1063
|
+
security: {
|
|
1064
|
+
async authenticate({ request }) {
|
|
1065
|
+
const user = await authenticateRequest(request);
|
|
1066
|
+
return user ? { id: user.id, scopes: ["docs:read"] } : null;
|
|
1067
|
+
},
|
|
1068
|
+
},
|
|
1069
|
+
},
|
|
495
1070
|
});`
|
|
496
1071
|
},
|
|
497
1072
|
{
|
|
@@ -516,7 +1091,7 @@ export default defineDocs({
|
|
|
516
1091
|
},
|
|
517
1092
|
});`
|
|
518
1093
|
}
|
|
519
|
-
];
|
|
1094
|
+
]);
|
|
520
1095
|
const searchDocsInputSchema = z.object({
|
|
521
1096
|
query: z.string().trim().min(1),
|
|
522
1097
|
limit: z.number().int().min(1).max(25).optional(),
|
|
@@ -524,8 +1099,74 @@ const searchDocsInputSchema = z.object({
|
|
|
524
1099
|
});
|
|
525
1100
|
const readPageInputSchema = z.object({
|
|
526
1101
|
path: z.string().min(1),
|
|
1102
|
+
locale: z.string().min(1).optional(),
|
|
1103
|
+
section: z.string().trim().min(1).optional(),
|
|
1104
|
+
maxChars: z.number().int().min(256).max(1e6).optional()
|
|
1105
|
+
});
|
|
1106
|
+
const listTasksInputSchema = z.object({
|
|
1107
|
+
query: z.string().trim().min(1).optional(),
|
|
1108
|
+
framework: z.string().trim().min(1).optional(),
|
|
1109
|
+
version: z.string().trim().min(1).optional(),
|
|
1110
|
+
package: z.string().trim().min(1).optional(),
|
|
527
1111
|
locale: z.string().min(1).optional()
|
|
528
1112
|
});
|
|
1113
|
+
const readTaskInputSchema = readPageInputSchema;
|
|
1114
|
+
const pageAgentAppliesToOutputSchema = z.object({
|
|
1115
|
+
framework: z.array(z.string()).optional(),
|
|
1116
|
+
version: z.array(z.string()).optional(),
|
|
1117
|
+
package: z.array(z.string()).optional()
|
|
1118
|
+
});
|
|
1119
|
+
const pageAgentCommandOutputSchema = z.union([z.string(), z.object({
|
|
1120
|
+
run: z.string(),
|
|
1121
|
+
cwd: z.string().optional(),
|
|
1122
|
+
description: z.string().optional()
|
|
1123
|
+
})]);
|
|
1124
|
+
const pageAgentVerificationOutputSchema = z.union([z.string(), z.object({
|
|
1125
|
+
description: z.string().optional(),
|
|
1126
|
+
run: z.string().optional(),
|
|
1127
|
+
expect: z.string().optional()
|
|
1128
|
+
})]);
|
|
1129
|
+
const pageAgentFailureModeOutputSchema = z.union([z.string(), z.object({
|
|
1130
|
+
symptom: z.string(),
|
|
1131
|
+
resolution: z.string().optional()
|
|
1132
|
+
})]);
|
|
1133
|
+
const pageAgentContractOutputSchema = z.object({
|
|
1134
|
+
tokenBudget: z.number().optional(),
|
|
1135
|
+
task: z.string().optional(),
|
|
1136
|
+
outcome: z.string().optional(),
|
|
1137
|
+
appliesTo: pageAgentAppliesToOutputSchema.optional(),
|
|
1138
|
+
prerequisites: z.array(z.string()).optional(),
|
|
1139
|
+
files: z.array(z.string()).optional(),
|
|
1140
|
+
commands: z.array(pageAgentCommandOutputSchema).optional(),
|
|
1141
|
+
sideEffects: z.array(z.string()).optional(),
|
|
1142
|
+
verification: z.array(pageAgentVerificationOutputSchema).optional(),
|
|
1143
|
+
rollback: z.array(z.string()).optional(),
|
|
1144
|
+
failureModes: z.array(pageAgentFailureModeOutputSchema).optional()
|
|
1145
|
+
});
|
|
1146
|
+
const taskSummaryOutputSchema = z.object({
|
|
1147
|
+
slug: z.string(),
|
|
1148
|
+
url: z.string(),
|
|
1149
|
+
title: z.string(),
|
|
1150
|
+
description: z.string().optional(),
|
|
1151
|
+
task: z.string().optional(),
|
|
1152
|
+
outcome: z.string().optional(),
|
|
1153
|
+
appliesTo: pageAgentAppliesToOutputSchema.optional()
|
|
1154
|
+
});
|
|
1155
|
+
const listTasksOutputSchema = z.object({
|
|
1156
|
+
resultCount: z.number().int().nonnegative(),
|
|
1157
|
+
tasks: z.array(taskSummaryOutputSchema)
|
|
1158
|
+
});
|
|
1159
|
+
const readTaskOutputSchema = z.object({
|
|
1160
|
+
page: z.object({
|
|
1161
|
+
slug: z.string(),
|
|
1162
|
+
url: z.string(),
|
|
1163
|
+
title: z.string(),
|
|
1164
|
+
description: z.string().optional(),
|
|
1165
|
+
sourcePath: z.string().optional(),
|
|
1166
|
+
lastModified: z.string().optional()
|
|
1167
|
+
}),
|
|
1168
|
+
contract: pageAgentContractOutputSchema
|
|
1169
|
+
});
|
|
529
1170
|
const listPagesInputSchema = z.object({ locale: z.string().min(1).optional() });
|
|
530
1171
|
const listDocsInputSchema = z.object({
|
|
531
1172
|
section: z.string().trim().min(1).optional(),
|
|
@@ -546,6 +1187,195 @@ const getCodeExamplesInputSchema = z.object({
|
|
|
546
1187
|
limit: z.number().int().min(1).max(50).optional(),
|
|
547
1188
|
locale: z.string().min(1).optional()
|
|
548
1189
|
});
|
|
1190
|
+
const getContextInputSchema = z.object({
|
|
1191
|
+
query: z.string().trim().min(1),
|
|
1192
|
+
framework: z.string().trim().min(1).optional(),
|
|
1193
|
+
version: z.string().trim().min(1).optional(),
|
|
1194
|
+
locale: z.string().trim().min(1).optional(),
|
|
1195
|
+
tokenBudget: z.number().int().min(MIN_MCP_CONTEXT_TOKEN_BUDGET).max(MAX_MCP_CONTEXT_TOKEN_BUDGET).default(DEFAULT_MCP_CONTEXT_TOKEN_BUDGET)
|
|
1196
|
+
});
|
|
1197
|
+
const relatedLinkOutputSchema = z.object({ href: z.string() });
|
|
1198
|
+
const pageAgentContractSummaryOutputSchema = z.object({
|
|
1199
|
+
hasContract: z.boolean(),
|
|
1200
|
+
task: z.string().optional(),
|
|
1201
|
+
outcome: z.string().optional(),
|
|
1202
|
+
appliesTo: pageAgentAppliesToOutputSchema.optional()
|
|
1203
|
+
});
|
|
1204
|
+
const pageSummaryOutputSchema = z.object({
|
|
1205
|
+
slug: z.string(),
|
|
1206
|
+
url: z.string(),
|
|
1207
|
+
title: z.string(),
|
|
1208
|
+
description: z.string().optional(),
|
|
1209
|
+
agent: pageAgentContractSummaryOutputSchema.optional(),
|
|
1210
|
+
icon: z.string().optional(),
|
|
1211
|
+
sourcePath: z.string().optional(),
|
|
1212
|
+
lastModified: z.string().optional()
|
|
1213
|
+
});
|
|
1214
|
+
const docsSectionOutputSchema = z.lazy(() => z.object({
|
|
1215
|
+
slug: z.string(),
|
|
1216
|
+
title: z.string(),
|
|
1217
|
+
url: z.string().optional(),
|
|
1218
|
+
description: z.string().optional(),
|
|
1219
|
+
icon: z.string().optional(),
|
|
1220
|
+
pageCount: z.number().int().nonnegative(),
|
|
1221
|
+
pages: z.array(pageSummaryOutputSchema),
|
|
1222
|
+
sections: z.array(docsSectionOutputSchema)
|
|
1223
|
+
}));
|
|
1224
|
+
const listPagesOutputSchema = z.object({ pages: z.array(pageSummaryOutputSchema) });
|
|
1225
|
+
const listDocsOutputSchema = z.object({
|
|
1226
|
+
section: z.string().optional(),
|
|
1227
|
+
resultCount: z.number().int().nonnegative(),
|
|
1228
|
+
sectionCount: z.number().int().nonnegative(),
|
|
1229
|
+
pages: z.array(pageSummaryOutputSchema),
|
|
1230
|
+
rootPages: z.array(pageSummaryOutputSchema),
|
|
1231
|
+
sections: z.array(docsSectionOutputSchema)
|
|
1232
|
+
});
|
|
1233
|
+
const navigationPageOutputSchema = z.object({
|
|
1234
|
+
type: z.literal("page"),
|
|
1235
|
+
name: z.string(),
|
|
1236
|
+
url: z.string(),
|
|
1237
|
+
icon: z.string().optional(),
|
|
1238
|
+
description: z.string().optional()
|
|
1239
|
+
});
|
|
1240
|
+
const navigationNodeOutputSchema = z.lazy(() => z.discriminatedUnion("type", [navigationPageOutputSchema, z.object({
|
|
1241
|
+
type: z.literal("folder"),
|
|
1242
|
+
name: z.string(),
|
|
1243
|
+
icon: z.string().optional(),
|
|
1244
|
+
index: navigationPageOutputSchema.optional(),
|
|
1245
|
+
children: z.array(navigationNodeOutputSchema)
|
|
1246
|
+
})]));
|
|
1247
|
+
const navigationOutputSchema = z.object({
|
|
1248
|
+
navigation: z.object({
|
|
1249
|
+
name: z.string(),
|
|
1250
|
+
children: z.array(navigationNodeOutputSchema)
|
|
1251
|
+
}),
|
|
1252
|
+
markdown: z.string()
|
|
1253
|
+
});
|
|
1254
|
+
const searchResultOutputSchema = z.object({
|
|
1255
|
+
id: z.string(),
|
|
1256
|
+
url: z.string(),
|
|
1257
|
+
content: z.string(),
|
|
1258
|
+
description: z.string().optional(),
|
|
1259
|
+
type: z.enum([
|
|
1260
|
+
"page",
|
|
1261
|
+
"heading",
|
|
1262
|
+
"text"
|
|
1263
|
+
]),
|
|
1264
|
+
score: z.number().optional(),
|
|
1265
|
+
section: z.string().optional()
|
|
1266
|
+
});
|
|
1267
|
+
const searchDocsOutputSchema = z.object({ results: z.array(searchResultOutputSchema) });
|
|
1268
|
+
const codeExampleOutputSchema = z.object({
|
|
1269
|
+
id: z.string(),
|
|
1270
|
+
page: z.object({
|
|
1271
|
+
slug: z.string(),
|
|
1272
|
+
url: z.string(),
|
|
1273
|
+
title: z.string(),
|
|
1274
|
+
description: z.string().optional(),
|
|
1275
|
+
sourcePath: z.string().optional(),
|
|
1276
|
+
lastModified: z.string().optional()
|
|
1277
|
+
}),
|
|
1278
|
+
language: z.string().optional(),
|
|
1279
|
+
title: z.string().optional(),
|
|
1280
|
+
framework: z.string().optional(),
|
|
1281
|
+
packageManager: z.string().optional(),
|
|
1282
|
+
runnable: z.boolean(),
|
|
1283
|
+
meta: z.record(z.string(), z.union([z.string(), z.boolean()])),
|
|
1284
|
+
code: z.string()
|
|
1285
|
+
});
|
|
1286
|
+
const codeExamplesOutputSchema = z.object({ examples: z.array(codeExampleOutputSchema) });
|
|
1287
|
+
const configSchemaOptionOutputSchema = z.lazy(() => z.object({
|
|
1288
|
+
path: z.string(),
|
|
1289
|
+
name: z.string(),
|
|
1290
|
+
type: z.string(),
|
|
1291
|
+
default: z.union([
|
|
1292
|
+
z.string(),
|
|
1293
|
+
z.boolean(),
|
|
1294
|
+
z.number(),
|
|
1295
|
+
z.null()
|
|
1296
|
+
]).optional(),
|
|
1297
|
+
description: z.string(),
|
|
1298
|
+
docs: z.string().optional(),
|
|
1299
|
+
values: z.array(z.string()).optional(),
|
|
1300
|
+
children: z.array(configSchemaOptionOutputSchema).optional()
|
|
1301
|
+
}));
|
|
1302
|
+
const configSchemaOutputSchema = z.object({
|
|
1303
|
+
schemaVersion: z.literal(1),
|
|
1304
|
+
configFile: z.literal("docs.config.ts"),
|
|
1305
|
+
description: z.string(),
|
|
1306
|
+
filters: z.object({
|
|
1307
|
+
option: z.string().optional(),
|
|
1308
|
+
query: z.string().optional()
|
|
1309
|
+
}).optional(),
|
|
1310
|
+
resultCount: z.number().int().nonnegative(),
|
|
1311
|
+
options: z.array(configSchemaOptionOutputSchema),
|
|
1312
|
+
examples: z.array(z.object({
|
|
1313
|
+
title: z.string(),
|
|
1314
|
+
code: z.string()
|
|
1315
|
+
}))
|
|
1316
|
+
});
|
|
1317
|
+
const readPageOutputSchema = z.object({
|
|
1318
|
+
page: z.object({
|
|
1319
|
+
slug: z.string(),
|
|
1320
|
+
url: z.string(),
|
|
1321
|
+
title: z.string(),
|
|
1322
|
+
description: z.string().optional(),
|
|
1323
|
+
related: z.array(relatedLinkOutputSchema).optional(),
|
|
1324
|
+
icon: z.string().optional(),
|
|
1325
|
+
sourcePath: z.string().optional(),
|
|
1326
|
+
lastModified: z.string().optional(),
|
|
1327
|
+
locale: z.string().optional(),
|
|
1328
|
+
framework: z.string().optional(),
|
|
1329
|
+
version: z.string().optional(),
|
|
1330
|
+
tags: z.array(z.string()).optional()
|
|
1331
|
+
}),
|
|
1332
|
+
document: z.string(),
|
|
1333
|
+
section: z.string().optional(),
|
|
1334
|
+
anchor: z.string().optional(),
|
|
1335
|
+
chars: z.number().int().nonnegative(),
|
|
1336
|
+
totalChars: z.number().int().nonnegative(),
|
|
1337
|
+
truncated: z.boolean()
|
|
1338
|
+
});
|
|
1339
|
+
const contextSourceOutputSchema = z.object({
|
|
1340
|
+
id: z.string(),
|
|
1341
|
+
title: z.string(),
|
|
1342
|
+
pageUrl: z.string(),
|
|
1343
|
+
url: z.string(),
|
|
1344
|
+
section: z.string().optional(),
|
|
1345
|
+
anchor: z.string().optional(),
|
|
1346
|
+
sourcePath: z.string().optional(),
|
|
1347
|
+
lastModified: z.string().optional(),
|
|
1348
|
+
locale: z.string().optional(),
|
|
1349
|
+
framework: z.string().optional(),
|
|
1350
|
+
version: z.string().optional(),
|
|
1351
|
+
tags: z.array(z.string()).optional(),
|
|
1352
|
+
score: z.number().optional(),
|
|
1353
|
+
content: z.string(),
|
|
1354
|
+
chars: z.number().int().nonnegative(),
|
|
1355
|
+
utf8Bytes: z.number().int().nonnegative(),
|
|
1356
|
+
truncated: z.boolean()
|
|
1357
|
+
});
|
|
1358
|
+
const contextOutputSchema = z.object({
|
|
1359
|
+
query: z.string(),
|
|
1360
|
+
filters: z.object({
|
|
1361
|
+
framework: z.string().optional(),
|
|
1362
|
+
version: z.string().optional(),
|
|
1363
|
+
locale: z.string().optional()
|
|
1364
|
+
}),
|
|
1365
|
+
budget: z.object({
|
|
1366
|
+
requestedTokens: z.number().int().positive(),
|
|
1367
|
+
strategy: z.literal("utf8-bytes"),
|
|
1368
|
+
maxUtf8Bytes: z.number().int().positive(),
|
|
1369
|
+
usedUtf8Bytes: z.number().int().nonnegative(),
|
|
1370
|
+
conservativeTokenUpperBound: z.number().int().nonnegative(),
|
|
1371
|
+
remainingUtf8Bytes: z.number().int().nonnegative(),
|
|
1372
|
+
truncated: z.boolean()
|
|
1373
|
+
}),
|
|
1374
|
+
resultCount: z.number().int().nonnegative(),
|
|
1375
|
+
candidateCount: z.number().int().nonnegative(),
|
|
1376
|
+
context: z.string(),
|
|
1377
|
+
sources: z.array(contextSourceOutputSchema)
|
|
1378
|
+
});
|
|
549
1379
|
function normalizeDocsMcpRoute(route) {
|
|
550
1380
|
if (!route || route.trim().length === 0) return DEFAULT_MCP_ROUTE;
|
|
551
1381
|
const normalized = `/${route}`.replace(/\/+/g, "/");
|
|
@@ -561,11 +1391,15 @@ function resolveDocsMcpConfig(mcp, defaults = {}) {
|
|
|
561
1391
|
listDocs: true,
|
|
562
1392
|
listPages: true,
|
|
563
1393
|
readPage: true,
|
|
1394
|
+
listTasks: true,
|
|
1395
|
+
readTask: true,
|
|
564
1396
|
searchDocs: true,
|
|
565
1397
|
getNavigation: true,
|
|
566
1398
|
getCodeExamples: true,
|
|
567
|
-
getConfigSchema: true
|
|
568
|
-
|
|
1399
|
+
getConfigSchema: true,
|
|
1400
|
+
getContext: true
|
|
1401
|
+
},
|
|
1402
|
+
security: resolveDocsMcpSecurityConfig()
|
|
569
1403
|
};
|
|
570
1404
|
const config = mcp && typeof mcp === "object" ? mcp : {};
|
|
571
1405
|
return {
|
|
@@ -577,13 +1411,49 @@ function resolveDocsMcpConfig(mcp, defaults = {}) {
|
|
|
577
1411
|
listDocs: config.tools?.listDocs ?? true,
|
|
578
1412
|
listPages: config.tools?.listPages ?? true,
|
|
579
1413
|
readPage: config.tools?.readPage ?? true,
|
|
1414
|
+
listTasks: config.tools?.listTasks ?? true,
|
|
1415
|
+
readTask: config.tools?.readTask ?? true,
|
|
580
1416
|
searchDocs: config.tools?.searchDocs ?? true,
|
|
581
1417
|
getNavigation: config.tools?.getNavigation ?? true,
|
|
582
1418
|
getCodeExamples: config.tools?.getCodeExamples ?? true,
|
|
583
|
-
getConfigSchema: config.tools?.getConfigSchema ?? true
|
|
584
|
-
|
|
1419
|
+
getConfigSchema: config.tools?.getConfigSchema ?? true,
|
|
1420
|
+
getContext: config.tools?.getContext ?? true
|
|
1421
|
+
},
|
|
1422
|
+
security: resolveDocsMcpSecurityConfig(config.security)
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
function resolveDocsMcpSecurityConfig(security) {
|
|
1426
|
+
const configuredMaxBodyBytes = security?.maxBodyBytes;
|
|
1427
|
+
const maxBodyBytes = typeof configuredMaxBodyBytes === "number" && Number.isFinite(configuredMaxBodyBytes) && configuredMaxBodyBytes > 0 ? Math.floor(configuredMaxBodyBytes) : DEFAULT_DOCS_MCP_MAX_BODY_BYTES;
|
|
1428
|
+
return {
|
|
1429
|
+
allowedOrigins: security?.allowedOrigins ?? "same-origin",
|
|
1430
|
+
authenticate: security?.authenticate,
|
|
1431
|
+
maxBodyBytes,
|
|
1432
|
+
cors: resolveDocsMcpCorsConfig(security?.cors)
|
|
585
1433
|
};
|
|
586
1434
|
}
|
|
1435
|
+
function resolveDocsMcpCorsConfig(cors) {
|
|
1436
|
+
const config = cors && typeof cors === "object" ? cors : {};
|
|
1437
|
+
const configuredMaxAge = config.maxAgeSeconds;
|
|
1438
|
+
const maxAgeSeconds = typeof configuredMaxAge === "number" && Number.isFinite(configuredMaxAge) && configuredMaxAge >= 0 ? Math.floor(configuredMaxAge) : DEFAULT_DOCS_MCP_CORS_MAX_AGE_SECONDS;
|
|
1439
|
+
return {
|
|
1440
|
+
enabled: cors !== false,
|
|
1441
|
+
allowedHeaders: mergeHttpHeaderNames(DEFAULT_DOCS_MCP_CORS_ALLOWED_HEADERS, config.allowedHeaders),
|
|
1442
|
+
exposedHeaders: mergeHttpHeaderNames(DEFAULT_DOCS_MCP_CORS_EXPOSED_HEADERS, config.exposedHeaders),
|
|
1443
|
+
allowCredentials: config.allowCredentials === true,
|
|
1444
|
+
maxAgeSeconds
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
function mergeHttpHeaderNames(defaults, configured) {
|
|
1448
|
+
const headers = /* @__PURE__ */ new Map();
|
|
1449
|
+
for (const header of [...defaults, ...configured ?? []]) {
|
|
1450
|
+
const normalized = header.trim();
|
|
1451
|
+
if (!/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(normalized)) continue;
|
|
1452
|
+
const key = normalized.toLowerCase();
|
|
1453
|
+
if (!headers.has(key)) headers.set(key, normalized);
|
|
1454
|
+
}
|
|
1455
|
+
return [...headers.values()];
|
|
1456
|
+
}
|
|
587
1457
|
function createFilesystemDocsMcpSource(options = {}) {
|
|
588
1458
|
const rootDir = options.rootDir ?? process.cwd();
|
|
589
1459
|
const entry = normalizePathSegment(options.entry ?? "docs") || "docs";
|
|
@@ -618,6 +1488,15 @@ function nowMs() {
|
|
|
618
1488
|
function durationMs(startedAt) {
|
|
619
1489
|
return Math.max(0, Date.now() - startedAt);
|
|
620
1490
|
}
|
|
1491
|
+
function createStructuredTextResult(structuredContent, text) {
|
|
1492
|
+
return {
|
|
1493
|
+
content: [{
|
|
1494
|
+
type: "text",
|
|
1495
|
+
text: text ?? JSON.stringify(structuredContent, null, 2)
|
|
1496
|
+
}],
|
|
1497
|
+
structuredContent
|
|
1498
|
+
};
|
|
1499
|
+
}
|
|
621
1500
|
async function createDocsMcpServer(options) {
|
|
622
1501
|
const resolved = resolveDocsMcpConfig(options.mcp, {
|
|
623
1502
|
defaultName: options.defaultName ?? options.source.siteTitle ?? DEFAULT_MCP_NAME,
|
|
@@ -634,6 +1513,12 @@ async function createDocsMcpServer(options) {
|
|
|
634
1513
|
search: options.search
|
|
635
1514
|
};
|
|
636
1515
|
const telemetryFramework = options.telemetryFramework ?? "mcp";
|
|
1516
|
+
function getSourcePages(locale) {
|
|
1517
|
+
return options.source.getPages(locale, options.requestContext);
|
|
1518
|
+
}
|
|
1519
|
+
function getSourceNavigation(locale) {
|
|
1520
|
+
return options.source.getNavigation(locale, options.requestContext);
|
|
1521
|
+
}
|
|
637
1522
|
function trackMcpTool(tool, values) {
|
|
638
1523
|
emitDocsTelemetryMcpToolEvent(telemetryConfig, {
|
|
639
1524
|
framework: telemetryFramework,
|
|
@@ -642,8 +1527,8 @@ async function createDocsMcpServer(options) {
|
|
|
642
1527
|
resultCount: values?.resultCount
|
|
643
1528
|
});
|
|
644
1529
|
}
|
|
645
|
-
const defaultPages = dedupePages(await
|
|
646
|
-
const defaultTree = await
|
|
1530
|
+
const defaultPages = dedupePages(await getSourcePages());
|
|
1531
|
+
const defaultTree = await getSourceNavigation();
|
|
647
1532
|
server.registerResource("docs-navigation", "docs://navigation", {
|
|
648
1533
|
title: "Docs Navigation",
|
|
649
1534
|
description: "Structured navigation tree for the documentation site.",
|
|
@@ -669,6 +1554,7 @@ async function createDocsMcpServer(options) {
|
|
|
669
1554
|
title: "List docs pages",
|
|
670
1555
|
description: "List the known documentation pages with titles, slugs, and URLs.",
|
|
671
1556
|
inputSchema: listPagesInputSchema,
|
|
1557
|
+
outputSchema: listPagesOutputSchema,
|
|
672
1558
|
annotations: { readOnlyHint: true }
|
|
673
1559
|
}, async ({ locale }) => {
|
|
674
1560
|
const startedAt = nowMs();
|
|
@@ -687,7 +1573,7 @@ async function createDocsMcpServer(options) {
|
|
|
687
1573
|
metadata: { tool: "list_pages" }
|
|
688
1574
|
});
|
|
689
1575
|
try {
|
|
690
|
-
const pages = toPageSummaries(dedupePages(await
|
|
1576
|
+
const pages = toPageSummaries(dedupePages(await getSourcePages(locale)));
|
|
691
1577
|
const elapsed = durationMs(startedAt);
|
|
692
1578
|
await emitDocsAnalyticsEvent(options.analytics, {
|
|
693
1579
|
type: "mcp_tool",
|
|
@@ -717,10 +1603,7 @@ async function createDocsMcpServer(options) {
|
|
|
717
1603
|
outputPreview: { resultCount: pages.length },
|
|
718
1604
|
metadata: { tool: "list_pages" }
|
|
719
1605
|
});
|
|
720
|
-
return {
|
|
721
|
-
type: "text",
|
|
722
|
-
text: JSON.stringify({ pages }, null, 2)
|
|
723
|
-
}] };
|
|
1606
|
+
return createStructuredTextResult({ pages });
|
|
724
1607
|
} catch (error) {
|
|
725
1608
|
const elapsed = durationMs(startedAt);
|
|
726
1609
|
await emitDocsAgentTraceEvent(options.observability, {
|
|
@@ -744,6 +1627,7 @@ async function createDocsMcpServer(options) {
|
|
|
744
1627
|
title: "List docs by section",
|
|
745
1628
|
description: "List documentation pages grouped by section, optionally narrowed to one section.",
|
|
746
1629
|
inputSchema: listDocsInputSchema,
|
|
1630
|
+
outputSchema: listDocsOutputSchema,
|
|
747
1631
|
annotations: { readOnlyHint: true }
|
|
748
1632
|
}, async ({ section, locale }) => {
|
|
749
1633
|
const startedAt = nowMs();
|
|
@@ -765,7 +1649,7 @@ async function createDocsMcpServer(options) {
|
|
|
765
1649
|
metadata: { tool: "list_docs" }
|
|
766
1650
|
});
|
|
767
1651
|
try {
|
|
768
|
-
const docs = listDocsBySection(dedupePages(await
|
|
1652
|
+
const docs = listDocsBySection(dedupePages(await getSourcePages(locale)), {
|
|
769
1653
|
section,
|
|
770
1654
|
entry: options.source.entry
|
|
771
1655
|
});
|
|
@@ -775,38 +1659,281 @@ async function createDocsMcpServer(options) {
|
|
|
775
1659
|
source: "mcp",
|
|
776
1660
|
locale,
|
|
777
1661
|
properties: {
|
|
778
|
-
tool: "list_docs",
|
|
779
|
-
section,
|
|
780
|
-
resultCount: docs.resultCount,
|
|
781
|
-
sectionCount: docs.sectionCount,
|
|
1662
|
+
tool: "list_docs",
|
|
1663
|
+
section,
|
|
1664
|
+
resultCount: docs.resultCount,
|
|
1665
|
+
sectionCount: docs.sectionCount,
|
|
1666
|
+
durationMs: elapsed
|
|
1667
|
+
}
|
|
1668
|
+
});
|
|
1669
|
+
trackMcpTool("list_docs", {
|
|
1670
|
+
locale,
|
|
1671
|
+
resultCount: docs.resultCount
|
|
1672
|
+
});
|
|
1673
|
+
await emitDocsAgentTraceEvent(options.observability, {
|
|
1674
|
+
type: "tool.result",
|
|
1675
|
+
source: "mcp",
|
|
1676
|
+
traceId: trace.traceId,
|
|
1677
|
+
parentSpanId: callSpanId,
|
|
1678
|
+
name: "list_docs",
|
|
1679
|
+
startedAt: trace.startedAt,
|
|
1680
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1681
|
+
durationMs: elapsed,
|
|
1682
|
+
status: "success",
|
|
1683
|
+
locale,
|
|
1684
|
+
outputPreview: {
|
|
1685
|
+
resultCount: docs.resultCount,
|
|
1686
|
+
sectionCount: docs.sectionCount
|
|
1687
|
+
},
|
|
1688
|
+
metadata: { tool: "list_docs" }
|
|
1689
|
+
});
|
|
1690
|
+
return createStructuredTextResult(docs);
|
|
1691
|
+
} catch (error) {
|
|
1692
|
+
const elapsed = durationMs(startedAt);
|
|
1693
|
+
await emitDocsAgentTraceEvent(options.observability, {
|
|
1694
|
+
type: "tool.error",
|
|
1695
|
+
source: "mcp",
|
|
1696
|
+
traceId: trace.traceId,
|
|
1697
|
+
parentSpanId: callSpanId,
|
|
1698
|
+
name: "list_docs",
|
|
1699
|
+
startedAt: trace.startedAt,
|
|
1700
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1701
|
+
durationMs: elapsed,
|
|
1702
|
+
status: "error",
|
|
1703
|
+
locale,
|
|
1704
|
+
outputPreview: { message: error instanceof Error ? error.message : "Unknown error" },
|
|
1705
|
+
metadata: { tool: "list_docs" }
|
|
1706
|
+
});
|
|
1707
|
+
throw error;
|
|
1708
|
+
}
|
|
1709
|
+
});
|
|
1710
|
+
if (resolved.tools.listTasks) server.registerTool("list_tasks", {
|
|
1711
|
+
title: "List documented tasks",
|
|
1712
|
+
description: "List pages with actionable agent contracts, optionally filtered by text or applicability.",
|
|
1713
|
+
inputSchema: listTasksInputSchema,
|
|
1714
|
+
outputSchema: listTasksOutputSchema,
|
|
1715
|
+
annotations: { readOnlyHint: true }
|
|
1716
|
+
}, async ({ query, framework, version, package: packageName, locale }) => {
|
|
1717
|
+
const startedAt = nowMs();
|
|
1718
|
+
const trace = createDocsAgentTraceContext("mcp.tool.list_tasks");
|
|
1719
|
+
const callSpanId = createDocsAgentTraceId("span");
|
|
1720
|
+
await emitDocsAgentTraceEvent(options.observability, {
|
|
1721
|
+
type: "tool.call",
|
|
1722
|
+
source: "mcp",
|
|
1723
|
+
traceId: trace.traceId,
|
|
1724
|
+
spanId: callSpanId,
|
|
1725
|
+
name: "list_tasks",
|
|
1726
|
+
startedAt: trace.startedAt,
|
|
1727
|
+
status: "started",
|
|
1728
|
+
locale,
|
|
1729
|
+
inputPreview: {
|
|
1730
|
+
queryLength: query?.length,
|
|
1731
|
+
framework,
|
|
1732
|
+
version,
|
|
1733
|
+
package: packageName
|
|
1734
|
+
},
|
|
1735
|
+
metadata: { tool: "list_tasks" }
|
|
1736
|
+
});
|
|
1737
|
+
try {
|
|
1738
|
+
const tasks = listDocsTasks(dedupePages(await getSourcePages(locale)), {
|
|
1739
|
+
query,
|
|
1740
|
+
framework,
|
|
1741
|
+
version,
|
|
1742
|
+
package: packageName
|
|
1743
|
+
});
|
|
1744
|
+
const result = {
|
|
1745
|
+
resultCount: tasks.length,
|
|
1746
|
+
tasks
|
|
1747
|
+
};
|
|
1748
|
+
const elapsed = durationMs(startedAt);
|
|
1749
|
+
await emitDocsAnalyticsEvent(options.analytics, {
|
|
1750
|
+
type: "mcp_tool",
|
|
1751
|
+
source: "mcp",
|
|
1752
|
+
locale,
|
|
1753
|
+
input: query ? { query } : void 0,
|
|
1754
|
+
properties: {
|
|
1755
|
+
tool: "list_tasks",
|
|
1756
|
+
framework,
|
|
1757
|
+
version,
|
|
1758
|
+
package: packageName,
|
|
1759
|
+
resultCount: tasks.length,
|
|
1760
|
+
durationMs: elapsed
|
|
1761
|
+
}
|
|
1762
|
+
});
|
|
1763
|
+
trackMcpTool("list_tasks", {
|
|
1764
|
+
locale,
|
|
1765
|
+
resultCount: tasks.length
|
|
1766
|
+
});
|
|
1767
|
+
await emitDocsAgentTraceEvent(options.observability, {
|
|
1768
|
+
type: "tool.result",
|
|
1769
|
+
source: "mcp",
|
|
1770
|
+
traceId: trace.traceId,
|
|
1771
|
+
parentSpanId: callSpanId,
|
|
1772
|
+
name: "list_tasks",
|
|
1773
|
+
startedAt: trace.startedAt,
|
|
1774
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1775
|
+
durationMs: elapsed,
|
|
1776
|
+
status: "success",
|
|
1777
|
+
locale,
|
|
1778
|
+
outputPreview: { resultCount: tasks.length },
|
|
1779
|
+
metadata: { tool: "list_tasks" }
|
|
1780
|
+
});
|
|
1781
|
+
return {
|
|
1782
|
+
structuredContent: result,
|
|
1783
|
+
content: [{
|
|
1784
|
+
type: "text",
|
|
1785
|
+
text: JSON.stringify(result, null, 2)
|
|
1786
|
+
}]
|
|
1787
|
+
};
|
|
1788
|
+
} catch (error) {
|
|
1789
|
+
const elapsed = durationMs(startedAt);
|
|
1790
|
+
await emitDocsAgentTraceEvent(options.observability, {
|
|
1791
|
+
type: "tool.error",
|
|
1792
|
+
source: "mcp",
|
|
1793
|
+
traceId: trace.traceId,
|
|
1794
|
+
parentSpanId: callSpanId,
|
|
1795
|
+
name: "list_tasks",
|
|
1796
|
+
startedAt: trace.startedAt,
|
|
1797
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1798
|
+
durationMs: elapsed,
|
|
1799
|
+
status: "error",
|
|
1800
|
+
locale,
|
|
1801
|
+
outputPreview: { message: error instanceof Error ? error.message : "Unknown error" },
|
|
1802
|
+
metadata: { tool: "list_tasks" }
|
|
1803
|
+
});
|
|
1804
|
+
throw error;
|
|
1805
|
+
}
|
|
1806
|
+
});
|
|
1807
|
+
if (resolved.tools.readTask) server.registerTool("read_task", {
|
|
1808
|
+
title: "Read a documented task",
|
|
1809
|
+
description: "Read the full structured agent contract for a page by slug or URL path.",
|
|
1810
|
+
inputSchema: readTaskInputSchema,
|
|
1811
|
+
outputSchema: readTaskOutputSchema,
|
|
1812
|
+
annotations: { readOnlyHint: true }
|
|
1813
|
+
}, async ({ path: requestedPath, locale }) => {
|
|
1814
|
+
const startedAt = nowMs();
|
|
1815
|
+
const trace = createDocsAgentTraceContext("mcp.tool.read_task");
|
|
1816
|
+
const callSpanId = createDocsAgentTraceId("span");
|
|
1817
|
+
await emitDocsAgentTraceEvent(options.observability, {
|
|
1818
|
+
type: "tool.call",
|
|
1819
|
+
source: "mcp",
|
|
1820
|
+
traceId: trace.traceId,
|
|
1821
|
+
spanId: callSpanId,
|
|
1822
|
+
name: "read_task",
|
|
1823
|
+
startedAt: trace.startedAt,
|
|
1824
|
+
status: "started",
|
|
1825
|
+
locale,
|
|
1826
|
+
inputPreview: {
|
|
1827
|
+
path: requestedPath,
|
|
1828
|
+
locale
|
|
1829
|
+
},
|
|
1830
|
+
metadata: { tool: "read_task" }
|
|
1831
|
+
});
|
|
1832
|
+
try {
|
|
1833
|
+
const page = findDocsPage(dedupePages(await getSourcePages(locale)), requestedPath, options.source.entry);
|
|
1834
|
+
const contract = normalizePageAgentFrontmatter(page?.agent);
|
|
1835
|
+
if (!page || !contract || !hasStructuredPageAgentContract(contract)) {
|
|
1836
|
+
const elapsed = durationMs(startedAt);
|
|
1837
|
+
const reason = page ? "contract_not_found" : "page_not_found";
|
|
1838
|
+
const errorResult = { error: page ? `The docs page matched "${requestedPath}", but it has no actionable agent contract.` : `No docs page matched "${requestedPath}".` };
|
|
1839
|
+
await emitDocsAnalyticsEvent(options.analytics, {
|
|
1840
|
+
type: "mcp_tool",
|
|
1841
|
+
source: "mcp",
|
|
1842
|
+
locale,
|
|
1843
|
+
properties: {
|
|
1844
|
+
tool: "read_task",
|
|
1845
|
+
path: requestedPath,
|
|
1846
|
+
found: false,
|
|
1847
|
+
reason,
|
|
1848
|
+
durationMs: elapsed
|
|
1849
|
+
}
|
|
1850
|
+
});
|
|
1851
|
+
trackMcpTool("read_task", {
|
|
1852
|
+
locale,
|
|
1853
|
+
resultCount: 0
|
|
1854
|
+
});
|
|
1855
|
+
await emitDocsAgentTraceEvent(options.observability, {
|
|
1856
|
+
type: "tool.error",
|
|
1857
|
+
source: "mcp",
|
|
1858
|
+
traceId: trace.traceId,
|
|
1859
|
+
parentSpanId: callSpanId,
|
|
1860
|
+
name: "read_task",
|
|
1861
|
+
startedAt: trace.startedAt,
|
|
1862
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1863
|
+
durationMs: elapsed,
|
|
1864
|
+
status: "error",
|
|
1865
|
+
locale,
|
|
1866
|
+
outputPreview: {
|
|
1867
|
+
found: false,
|
|
1868
|
+
path: requestedPath
|
|
1869
|
+
},
|
|
1870
|
+
metadata: {
|
|
1871
|
+
tool: "read_task",
|
|
1872
|
+
reason
|
|
1873
|
+
}
|
|
1874
|
+
});
|
|
1875
|
+
return {
|
|
1876
|
+
content: [{
|
|
1877
|
+
type: "text",
|
|
1878
|
+
text: JSON.stringify(errorResult, null, 2)
|
|
1879
|
+
}],
|
|
1880
|
+
isError: true
|
|
1881
|
+
};
|
|
1882
|
+
}
|
|
1883
|
+
const result = {
|
|
1884
|
+
page: {
|
|
1885
|
+
slug: page.slug,
|
|
1886
|
+
url: page.url,
|
|
1887
|
+
title: page.title,
|
|
1888
|
+
...page.description ? { description: page.description } : {},
|
|
1889
|
+
...page.sourcePath ? { sourcePath: page.sourcePath } : {},
|
|
1890
|
+
...page.lastModified ? { lastModified: page.lastModified } : {}
|
|
1891
|
+
},
|
|
1892
|
+
contract
|
|
1893
|
+
};
|
|
1894
|
+
const elapsed = durationMs(startedAt);
|
|
1895
|
+
await emitDocsAnalyticsEvent(options.analytics, {
|
|
1896
|
+
type: "mcp_tool",
|
|
1897
|
+
source: "mcp",
|
|
1898
|
+
locale,
|
|
1899
|
+
path: page.url,
|
|
1900
|
+
properties: {
|
|
1901
|
+
tool: "read_task",
|
|
1902
|
+
requestedPath,
|
|
1903
|
+
slug: page.slug,
|
|
1904
|
+
found: true,
|
|
782
1905
|
durationMs: elapsed
|
|
783
1906
|
}
|
|
784
1907
|
});
|
|
785
|
-
trackMcpTool("
|
|
1908
|
+
trackMcpTool("read_task", {
|
|
786
1909
|
locale,
|
|
787
|
-
resultCount:
|
|
1910
|
+
resultCount: 1
|
|
788
1911
|
});
|
|
789
1912
|
await emitDocsAgentTraceEvent(options.observability, {
|
|
790
1913
|
type: "tool.result",
|
|
791
1914
|
source: "mcp",
|
|
792
1915
|
traceId: trace.traceId,
|
|
793
1916
|
parentSpanId: callSpanId,
|
|
794
|
-
name: "
|
|
1917
|
+
name: "read_task",
|
|
795
1918
|
startedAt: trace.startedAt,
|
|
796
1919
|
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
797
1920
|
durationMs: elapsed,
|
|
798
1921
|
status: "success",
|
|
799
1922
|
locale,
|
|
1923
|
+
path: page.url,
|
|
800
1924
|
outputPreview: {
|
|
801
|
-
|
|
802
|
-
|
|
1925
|
+
found: true,
|
|
1926
|
+
slug: page.slug
|
|
803
1927
|
},
|
|
804
|
-
metadata: { tool: "
|
|
1928
|
+
metadata: { tool: "read_task" }
|
|
805
1929
|
});
|
|
806
|
-
return {
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
1930
|
+
return {
|
|
1931
|
+
structuredContent: result,
|
|
1932
|
+
content: [{
|
|
1933
|
+
type: "text",
|
|
1934
|
+
text: JSON.stringify(result, null, 2)
|
|
1935
|
+
}]
|
|
1936
|
+
};
|
|
810
1937
|
} catch (error) {
|
|
811
1938
|
const elapsed = durationMs(startedAt);
|
|
812
1939
|
await emitDocsAgentTraceEvent(options.observability, {
|
|
@@ -814,14 +1941,14 @@ async function createDocsMcpServer(options) {
|
|
|
814
1941
|
source: "mcp",
|
|
815
1942
|
traceId: trace.traceId,
|
|
816
1943
|
parentSpanId: callSpanId,
|
|
817
|
-
name: "
|
|
1944
|
+
name: "read_task",
|
|
818
1945
|
startedAt: trace.startedAt,
|
|
819
1946
|
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
820
1947
|
durationMs: elapsed,
|
|
821
1948
|
status: "error",
|
|
822
1949
|
locale,
|
|
823
1950
|
outputPreview: { message: error instanceof Error ? error.message : "Unknown error" },
|
|
824
|
-
metadata: { tool: "
|
|
1951
|
+
metadata: { tool: "read_task" }
|
|
825
1952
|
});
|
|
826
1953
|
throw error;
|
|
827
1954
|
}
|
|
@@ -830,6 +1957,7 @@ async function createDocsMcpServer(options) {
|
|
|
830
1957
|
title: "Get docs navigation",
|
|
831
1958
|
description: "Return the documentation navigation tree for the current docs site.",
|
|
832
1959
|
inputSchema: getNavigationInputSchema,
|
|
1960
|
+
outputSchema: navigationOutputSchema,
|
|
833
1961
|
annotations: { readOnlyHint: true }
|
|
834
1962
|
}, async ({ locale }) => {
|
|
835
1963
|
const startedAt = nowMs();
|
|
@@ -848,7 +1976,8 @@ async function createDocsMcpServer(options) {
|
|
|
848
1976
|
metadata: { tool: "get_navigation" }
|
|
849
1977
|
});
|
|
850
1978
|
try {
|
|
851
|
-
const
|
|
1979
|
+
const tree = await getSourceNavigation(locale);
|
|
1980
|
+
const text = renderNavigationTree(tree);
|
|
852
1981
|
const elapsed = durationMs(startedAt);
|
|
853
1982
|
await emitDocsAnalyticsEvent(options.analytics, {
|
|
854
1983
|
type: "mcp_tool",
|
|
@@ -874,10 +2003,10 @@ async function createDocsMcpServer(options) {
|
|
|
874
2003
|
outputPreview: { chars: text.length },
|
|
875
2004
|
metadata: { tool: "get_navigation" }
|
|
876
2005
|
});
|
|
877
|
-
return {
|
|
878
|
-
|
|
879
|
-
text
|
|
880
|
-
}
|
|
2006
|
+
return createStructuredTextResult({
|
|
2007
|
+
navigation: tree,
|
|
2008
|
+
markdown: text
|
|
2009
|
+
}, text);
|
|
881
2010
|
} catch (error) {
|
|
882
2011
|
const elapsed = durationMs(startedAt);
|
|
883
2012
|
await emitDocsAgentTraceEvent(options.observability, {
|
|
@@ -901,6 +2030,7 @@ async function createDocsMcpServer(options) {
|
|
|
901
2030
|
title: "Get docs config schema",
|
|
902
2031
|
description: "Return structured docs.config.ts option metadata, optionally filtered by option path or query.",
|
|
903
2032
|
inputSchema: getConfigSchemaInputSchema,
|
|
2033
|
+
outputSchema: configSchemaOutputSchema,
|
|
904
2034
|
annotations: { readOnlyHint: true }
|
|
905
2035
|
}, async ({ option, query }) => {
|
|
906
2036
|
const startedAt = nowMs();
|
|
@@ -952,10 +2082,7 @@ async function createDocsMcpServer(options) {
|
|
|
952
2082
|
outputPreview: { resultCount: schema.resultCount },
|
|
953
2083
|
metadata: { tool: "get_config_schema" }
|
|
954
2084
|
});
|
|
955
|
-
return
|
|
956
|
-
type: "text",
|
|
957
|
-
text: JSON.stringify(schema, null, 2)
|
|
958
|
-
}] };
|
|
2085
|
+
return createStructuredTextResult(schema);
|
|
959
2086
|
} catch (error) {
|
|
960
2087
|
const elapsed = durationMs(startedAt);
|
|
961
2088
|
await emitDocsAgentTraceEvent(options.observability, {
|
|
@@ -978,6 +2105,7 @@ async function createDocsMcpServer(options) {
|
|
|
978
2105
|
title: "Search documentation",
|
|
979
2106
|
description: "Search the docs by keyword across titles, descriptions, and page content.",
|
|
980
2107
|
inputSchema: searchDocsInputSchema,
|
|
2108
|
+
outputSchema: searchDocsOutputSchema,
|
|
981
2109
|
annotations: { readOnlyHint: true }
|
|
982
2110
|
}, async ({ query, limit, locale }) => {
|
|
983
2111
|
const startedAt = nowMs();
|
|
@@ -1002,7 +2130,7 @@ async function createDocsMcpServer(options) {
|
|
|
1002
2130
|
});
|
|
1003
2131
|
try {
|
|
1004
2132
|
const results = await performDocsSearch({
|
|
1005
|
-
pages: toSearchSourcePages(dedupePages(await
|
|
2133
|
+
pages: toSearchSourcePages(dedupePages(await getSourcePages(locale))),
|
|
1006
2134
|
query,
|
|
1007
2135
|
search: toolSearchConfig ?? true,
|
|
1008
2136
|
locale,
|
|
@@ -1041,10 +2169,7 @@ async function createDocsMcpServer(options) {
|
|
|
1041
2169
|
outputPreview: { resultCount: results.length },
|
|
1042
2170
|
metadata: { tool: "search_docs" }
|
|
1043
2171
|
});
|
|
1044
|
-
return {
|
|
1045
|
-
type: "text",
|
|
1046
|
-
text: JSON.stringify({ results }, null, 2)
|
|
1047
|
-
}] };
|
|
2172
|
+
return createStructuredTextResult({ results });
|
|
1048
2173
|
} catch (error) {
|
|
1049
2174
|
const elapsed = durationMs(startedAt);
|
|
1050
2175
|
await emitDocsAgentTraceEvent(options.observability, {
|
|
@@ -1068,6 +2193,7 @@ async function createDocsMcpServer(options) {
|
|
|
1068
2193
|
title: "Get docs code examples",
|
|
1069
2194
|
description: "Return fenced code examples from the docs with parsed metadata such as title, framework, packageManager, and runnable.",
|
|
1070
2195
|
inputSchema: getCodeExamplesInputSchema,
|
|
2196
|
+
outputSchema: codeExamplesOutputSchema,
|
|
1071
2197
|
annotations: { readOnlyHint: true }
|
|
1072
2198
|
}, async ({ query, path: requestedPath, framework, packageManager, language, runnable, limit, locale }) => {
|
|
1073
2199
|
const startedAt = nowMs();
|
|
@@ -1095,7 +2221,7 @@ async function createDocsMcpServer(options) {
|
|
|
1095
2221
|
metadata: { tool: "get_code_examples" }
|
|
1096
2222
|
});
|
|
1097
2223
|
try {
|
|
1098
|
-
const pages = dedupePages(await
|
|
2224
|
+
const pages = dedupePages(await getSourcePages(locale));
|
|
1099
2225
|
const matchedPage = requestedPath ? findDocsPage(pages, requestedPath, options.source.entry) : null;
|
|
1100
2226
|
const examples = filterDocsCodeExamples((requestedPath ? matchedPage ? [matchedPage] : [] : pages).flatMap((page) => extractDocsMcpCodeExamples(page)), {
|
|
1101
2227
|
query,
|
|
@@ -1142,10 +2268,7 @@ async function createDocsMcpServer(options) {
|
|
|
1142
2268
|
outputPreview: { resultCount: examples.length },
|
|
1143
2269
|
metadata: { tool: "get_code_examples" }
|
|
1144
2270
|
});
|
|
1145
|
-
return {
|
|
1146
|
-
type: "text",
|
|
1147
|
-
text: JSON.stringify({ examples }, null, 2)
|
|
1148
|
-
}] };
|
|
2271
|
+
return createStructuredTextResult({ examples });
|
|
1149
2272
|
} catch (error) {
|
|
1150
2273
|
const elapsed = durationMs(startedAt);
|
|
1151
2274
|
await emitDocsAgentTraceEvent(options.observability, {
|
|
@@ -1165,12 +2288,115 @@ async function createDocsMcpServer(options) {
|
|
|
1165
2288
|
throw error;
|
|
1166
2289
|
}
|
|
1167
2290
|
});
|
|
2291
|
+
if (resolved.tools.getContext) server.registerTool("get_context", {
|
|
2292
|
+
title: "Get budgeted docs context",
|
|
2293
|
+
description: "Build deterministic, section-level documentation context for a query within a conservative UTF-8 byte ceiling derived from the requested token budget, with source URLs and accounting metadata.",
|
|
2294
|
+
inputSchema: getContextInputSchema,
|
|
2295
|
+
outputSchema: contextOutputSchema,
|
|
2296
|
+
annotations: { readOnlyHint: true }
|
|
2297
|
+
}, async ({ query, framework, version, locale, tokenBudget }) => {
|
|
2298
|
+
const startedAt = nowMs();
|
|
2299
|
+
const trace = createDocsAgentTraceContext("mcp.tool.get_context");
|
|
2300
|
+
const callSpanId = createDocsAgentTraceId("span");
|
|
2301
|
+
await emitDocsAgentTraceEvent(options.observability, {
|
|
2302
|
+
type: "tool.call",
|
|
2303
|
+
source: "mcp",
|
|
2304
|
+
traceId: trace.traceId,
|
|
2305
|
+
spanId: callSpanId,
|
|
2306
|
+
name: "get_context",
|
|
2307
|
+
startedAt: trace.startedAt,
|
|
2308
|
+
status: "started",
|
|
2309
|
+
locale,
|
|
2310
|
+
inputPreview: {
|
|
2311
|
+
queryLength: query.length,
|
|
2312
|
+
framework,
|
|
2313
|
+
version,
|
|
2314
|
+
locale,
|
|
2315
|
+
tokenBudget
|
|
2316
|
+
},
|
|
2317
|
+
metadata: { tool: "get_context" }
|
|
2318
|
+
});
|
|
2319
|
+
try {
|
|
2320
|
+
const result = await buildDocsMcpContext({
|
|
2321
|
+
pages: dedupePages(await getSourcePages(locale)),
|
|
2322
|
+
query,
|
|
2323
|
+
framework,
|
|
2324
|
+
version,
|
|
2325
|
+
locale,
|
|
2326
|
+
tokenBudget,
|
|
2327
|
+
entry: options.source.entry,
|
|
2328
|
+
siteTitle: options.source.siteTitle
|
|
2329
|
+
});
|
|
2330
|
+
const elapsed = durationMs(startedAt);
|
|
2331
|
+
await emitDocsAnalyticsEvent(options.analytics, {
|
|
2332
|
+
type: "mcp_tool",
|
|
2333
|
+
source: "mcp",
|
|
2334
|
+
locale,
|
|
2335
|
+
input: { query },
|
|
2336
|
+
properties: {
|
|
2337
|
+
tool: "get_context",
|
|
2338
|
+
queryLength: query.length,
|
|
2339
|
+
framework,
|
|
2340
|
+
version,
|
|
2341
|
+
tokenBudget,
|
|
2342
|
+
usedUtf8Bytes: result.budget.usedUtf8Bytes,
|
|
2343
|
+
conservativeTokenUpperBound: result.budget.conservativeTokenUpperBound,
|
|
2344
|
+
truncated: result.budget.truncated,
|
|
2345
|
+
resultCount: result.resultCount,
|
|
2346
|
+
durationMs: elapsed
|
|
2347
|
+
}
|
|
2348
|
+
});
|
|
2349
|
+
trackMcpTool("get_context", {
|
|
2350
|
+
locale,
|
|
2351
|
+
resultCount: result.resultCount
|
|
2352
|
+
});
|
|
2353
|
+
await emitDocsAgentTraceEvent(options.observability, {
|
|
2354
|
+
type: "tool.result",
|
|
2355
|
+
source: "mcp",
|
|
2356
|
+
traceId: trace.traceId,
|
|
2357
|
+
parentSpanId: callSpanId,
|
|
2358
|
+
name: "get_context",
|
|
2359
|
+
startedAt: trace.startedAt,
|
|
2360
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2361
|
+
durationMs: elapsed,
|
|
2362
|
+
status: "success",
|
|
2363
|
+
locale,
|
|
2364
|
+
outputPreview: {
|
|
2365
|
+
resultCount: result.resultCount,
|
|
2366
|
+
candidateCount: result.candidateCount,
|
|
2367
|
+
utf8Bytes: result.budget.usedUtf8Bytes,
|
|
2368
|
+
conservativeTokenUpperBound: result.budget.conservativeTokenUpperBound,
|
|
2369
|
+
truncated: result.budget.truncated
|
|
2370
|
+
},
|
|
2371
|
+
metadata: { tool: "get_context" }
|
|
2372
|
+
});
|
|
2373
|
+
return createStructuredTextResult(result, result.context || JSON.stringify(result, null, 2));
|
|
2374
|
+
} catch (error) {
|
|
2375
|
+
const elapsed = durationMs(startedAt);
|
|
2376
|
+
await emitDocsAgentTraceEvent(options.observability, {
|
|
2377
|
+
type: "tool.error",
|
|
2378
|
+
source: "mcp",
|
|
2379
|
+
traceId: trace.traceId,
|
|
2380
|
+
parentSpanId: callSpanId,
|
|
2381
|
+
name: "get_context",
|
|
2382
|
+
startedAt: trace.startedAt,
|
|
2383
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2384
|
+
durationMs: elapsed,
|
|
2385
|
+
status: "error",
|
|
2386
|
+
locale,
|
|
2387
|
+
outputPreview: { message: error instanceof Error ? error.message : "Unknown error" },
|
|
2388
|
+
metadata: { tool: "get_context" }
|
|
2389
|
+
});
|
|
2390
|
+
throw error;
|
|
2391
|
+
}
|
|
2392
|
+
});
|
|
1168
2393
|
if (resolved.tools.readPage) server.registerTool("read_page", {
|
|
1169
2394
|
title: "Read a docs page",
|
|
1170
|
-
description: "Read a documentation page by slug or URL path.",
|
|
2395
|
+
description: "Read a documentation page by slug or URL path, optionally selecting one heading and limiting returned characters.",
|
|
1171
2396
|
inputSchema: readPageInputSchema,
|
|
2397
|
+
outputSchema: readPageOutputSchema,
|
|
1172
2398
|
annotations: { readOnlyHint: true }
|
|
1173
|
-
}, async ({ path: requestedPath, locale }) => {
|
|
2399
|
+
}, async ({ path: requestedPath, locale, section, maxChars }) => {
|
|
1174
2400
|
const startedAt = nowMs();
|
|
1175
2401
|
const trace = createDocsAgentTraceContext("mcp.tool.read_page");
|
|
1176
2402
|
const callSpanId = createDocsAgentTraceId("span");
|
|
@@ -1185,12 +2411,14 @@ async function createDocsMcpServer(options) {
|
|
|
1185
2411
|
locale,
|
|
1186
2412
|
inputPreview: {
|
|
1187
2413
|
path: requestedPath,
|
|
1188
|
-
locale
|
|
2414
|
+
locale,
|
|
2415
|
+
section,
|
|
2416
|
+
maxChars
|
|
1189
2417
|
},
|
|
1190
2418
|
metadata: { tool: "read_page" }
|
|
1191
2419
|
});
|
|
1192
2420
|
try {
|
|
1193
|
-
const page = findDocsPage(dedupePages(await
|
|
2421
|
+
const page = findDocsPage(dedupePages(await getSourcePages(locale)), requestedPath, options.source.entry);
|
|
1194
2422
|
if (!page) {
|
|
1195
2423
|
const elapsed = durationMs(startedAt);
|
|
1196
2424
|
await emitDocsAnalyticsEvent(options.analytics, {
|
|
@@ -1248,7 +2476,59 @@ async function createDocsMcpServer(options) {
|
|
|
1248
2476
|
isError: true
|
|
1249
2477
|
};
|
|
1250
2478
|
}
|
|
1251
|
-
const
|
|
2479
|
+
const fullDocument = renderPageDocument(page);
|
|
2480
|
+
const sectionDocument = getDocsMcpSourceMarkdown(page);
|
|
2481
|
+
const selectedSection = section ? findDocsMarkdownSection(sectionDocument, section) ?? (sectionDocument !== fullDocument ? findDocsMarkdownSection(fullDocument, section) : void 0) : void 0;
|
|
2482
|
+
if (section && !selectedSection) {
|
|
2483
|
+
const sourceSections = parseDocsMarkdownSections(sectionDocument);
|
|
2484
|
+
const availableSections = (sourceSections.length > 0 ? sourceSections : parseDocsMarkdownSections(fullDocument)).map((item) => ({
|
|
2485
|
+
title: item.title,
|
|
2486
|
+
anchor: item.anchor
|
|
2487
|
+
}));
|
|
2488
|
+
const errorText = renderBoundedDocsMcpSectionError({
|
|
2489
|
+
requestedSection: section,
|
|
2490
|
+
pageUrl: page.url,
|
|
2491
|
+
availableSections,
|
|
2492
|
+
maxChars
|
|
2493
|
+
});
|
|
2494
|
+
const elapsed = durationMs(startedAt);
|
|
2495
|
+
trackMcpTool("read_page", {
|
|
2496
|
+
locale,
|
|
2497
|
+
resultCount: 0
|
|
2498
|
+
});
|
|
2499
|
+
await emitDocsAgentTraceEvent(options.observability, {
|
|
2500
|
+
type: "tool.error",
|
|
2501
|
+
source: "mcp",
|
|
2502
|
+
traceId: trace.traceId,
|
|
2503
|
+
parentSpanId: callSpanId,
|
|
2504
|
+
name: "read_page",
|
|
2505
|
+
startedAt: trace.startedAt,
|
|
2506
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2507
|
+
durationMs: elapsed,
|
|
2508
|
+
status: "error",
|
|
2509
|
+
locale,
|
|
2510
|
+
path: page.url,
|
|
2511
|
+
outputPreview: {
|
|
2512
|
+
found: true,
|
|
2513
|
+
sectionFound: false,
|
|
2514
|
+
section
|
|
2515
|
+
},
|
|
2516
|
+
metadata: {
|
|
2517
|
+
tool: "read_page",
|
|
2518
|
+
reason: "section_not_found"
|
|
2519
|
+
}
|
|
2520
|
+
});
|
|
2521
|
+
return {
|
|
2522
|
+
content: [{
|
|
2523
|
+
type: "text",
|
|
2524
|
+
text: errorText
|
|
2525
|
+
}],
|
|
2526
|
+
isError: true
|
|
2527
|
+
};
|
|
2528
|
+
}
|
|
2529
|
+
const selectedDocument = selectedSection?.content ?? fullDocument;
|
|
2530
|
+
const limitedDocument = limitDocsMcpText(selectedDocument, maxChars);
|
|
2531
|
+
const document = limitedDocument.text;
|
|
1252
2532
|
const elapsed = durationMs(startedAt);
|
|
1253
2533
|
await emitDocsAnalyticsEvent(options.analytics, {
|
|
1254
2534
|
type: "agent_read",
|
|
@@ -1261,7 +2541,10 @@ async function createDocsMcpServer(options) {
|
|
|
1261
2541
|
requestedPath,
|
|
1262
2542
|
slug: page.slug,
|
|
1263
2543
|
found: true,
|
|
2544
|
+
section: selectedSection?.title,
|
|
1264
2545
|
contentLength: document.length,
|
|
2546
|
+
totalContentLength: selectedDocument.length,
|
|
2547
|
+
truncated: limitedDocument.truncated,
|
|
1265
2548
|
durationMs: elapsed
|
|
1266
2549
|
}
|
|
1267
2550
|
});
|
|
@@ -1275,7 +2558,10 @@ async function createDocsMcpServer(options) {
|
|
|
1275
2558
|
requestedPath,
|
|
1276
2559
|
slug: page.slug,
|
|
1277
2560
|
found: true,
|
|
2561
|
+
section: selectedSection?.title,
|
|
1278
2562
|
contentLength: document.length,
|
|
2563
|
+
totalContentLength: selectedDocument.length,
|
|
2564
|
+
truncated: limitedDocument.truncated,
|
|
1279
2565
|
durationMs: elapsed
|
|
1280
2566
|
}
|
|
1281
2567
|
});
|
|
@@ -1298,14 +2584,22 @@ async function createDocsMcpServer(options) {
|
|
|
1298
2584
|
outputPreview: {
|
|
1299
2585
|
found: true,
|
|
1300
2586
|
chars: document.length,
|
|
2587
|
+
totalChars: selectedDocument.length,
|
|
2588
|
+
truncated: limitedDocument.truncated,
|
|
2589
|
+
section: selectedSection?.title,
|
|
1301
2590
|
slug: page.slug
|
|
1302
2591
|
},
|
|
1303
2592
|
metadata: { tool: "read_page" }
|
|
1304
2593
|
});
|
|
1305
|
-
return {
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
2594
|
+
return createStructuredTextResult({
|
|
2595
|
+
page: toStructuredDocsMcpPage(page),
|
|
2596
|
+
document,
|
|
2597
|
+
section: selectedSection?.title,
|
|
2598
|
+
anchor: selectedSection?.anchor,
|
|
2599
|
+
chars: document.length,
|
|
2600
|
+
totalChars: selectedDocument.length,
|
|
2601
|
+
truncated: limitedDocument.truncated
|
|
2602
|
+
}, document);
|
|
1309
2603
|
} catch (error) {
|
|
1310
2604
|
const elapsed = durationMs(startedAt);
|
|
1311
2605
|
await emitDocsAgentTraceEvent(options.observability, {
|
|
@@ -1348,10 +2642,14 @@ function createDocsMcpHttpHandler(options) {
|
|
|
1348
2642
|
id: readJsonRpcId(await parseJsonBody(request)),
|
|
1349
2643
|
data: { reason: "mcp_disabled" }
|
|
1350
2644
|
}),
|
|
1351
|
-
DELETE: async () => createJsonErrorResponse(404, disabledMessage)
|
|
2645
|
+
DELETE: async () => createJsonErrorResponse(404, disabledMessage),
|
|
2646
|
+
OPTIONS: async () => createJsonErrorResponse(404, disabledMessage)
|
|
1352
2647
|
};
|
|
1353
|
-
async function createStatelessTransport() {
|
|
1354
|
-
const server = await createDocsMcpServer(
|
|
2648
|
+
async function createStatelessTransport(requestContext) {
|
|
2649
|
+
const server = await createDocsMcpServer({
|
|
2650
|
+
...options,
|
|
2651
|
+
requestContext
|
|
2652
|
+
});
|
|
1355
2653
|
const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
|
|
1356
2654
|
await server.connect(transport);
|
|
1357
2655
|
return {
|
|
@@ -1362,15 +2660,38 @@ function createDocsMcpHttpHandler(options) {
|
|
|
1362
2660
|
async function handle(request) {
|
|
1363
2661
|
const url = new URL(request.url);
|
|
1364
2662
|
const method = request.method.toUpperCase();
|
|
1365
|
-
const
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
2663
|
+
const security = resolved.security ?? resolveDocsMcpSecurityConfig();
|
|
2664
|
+
const prepared = await prepareDocsMcpHttpRequest(request, security.maxBodyBytes);
|
|
2665
|
+
if (prepared.status === "too-large") return createMcpRequestTooLargeResponse(security.maxBodyBytes);
|
|
2666
|
+
request = prepared.request;
|
|
2667
|
+
let originAllowed;
|
|
2668
|
+
try {
|
|
2669
|
+
originAllowed = await isDocsMcpOriginAllowed(request.clone(), security.allowedOrigins);
|
|
1370
2670
|
} catch {
|
|
1371
|
-
|
|
1372
|
-
parsedBody = void 0;
|
|
2671
|
+
return createMcpHttpSecurityErrorResponse(500, "MCP Origin policy failed");
|
|
1373
2672
|
}
|
|
2673
|
+
if (!originAllowed) return createMcpHttpSecurityErrorResponse(403, "Forbidden Origin");
|
|
2674
|
+
if (method === "OPTIONS") return createDocsMcpOptionsResponse(request, security.cors);
|
|
2675
|
+
const withCors = (response) => applyDocsMcpCorsHeaders(response, request, security.cors);
|
|
2676
|
+
let auth;
|
|
2677
|
+
if (security.authenticate) {
|
|
2678
|
+
let authentication;
|
|
2679
|
+
try {
|
|
2680
|
+
authentication = await security.authenticate({
|
|
2681
|
+
request: request.clone(),
|
|
2682
|
+
pathname: url.pathname
|
|
2683
|
+
});
|
|
2684
|
+
} catch {
|
|
2685
|
+
return withCors(createMcpHttpSecurityErrorResponse(500, "MCP authentication failed"));
|
|
2686
|
+
}
|
|
2687
|
+
if (authentication instanceof Response) return withCors(authentication);
|
|
2688
|
+
if (authentication === null || authentication === void 0) return withCors(createMcpHttpSecurityErrorResponse(401, "Unauthorized"));
|
|
2689
|
+
if (!isDocsMcpAuthPrincipal(authentication)) return withCors(createMcpHttpSecurityErrorResponse(500, "MCP authentication returned an invalid principal"));
|
|
2690
|
+
auth = authentication;
|
|
2691
|
+
}
|
|
2692
|
+
const sessionId = request.headers.get("mcp-session-id") ?? request.headers.get("Mcp-Session-Id");
|
|
2693
|
+
const parsedBody = prepared.parsedBody;
|
|
2694
|
+
const bodyParseFailed = prepared.bodyParseFailed;
|
|
1374
2695
|
const initializeRequest = method === "POST" && parsedBody && isInitializeRequest(parsedBody);
|
|
1375
2696
|
emitDocsTelemetryProjectEvent(telemetryConfig, {
|
|
1376
2697
|
framework: telemetryFramework,
|
|
@@ -1397,24 +2718,220 @@ function createDocsMcpHttpHandler(options) {
|
|
|
1397
2718
|
initialize: Boolean(initializeRequest)
|
|
1398
2719
|
}
|
|
1399
2720
|
});
|
|
1400
|
-
if (method === "POST" && bodyParseFailed) return createJsonRpcErrorResponse({
|
|
2721
|
+
if (method === "POST" && bodyParseFailed) return withCors(createJsonRpcErrorResponse({
|
|
1401
2722
|
status: 400,
|
|
1402
2723
|
code: -32700,
|
|
1403
2724
|
message: "Parse error: Invalid JSON"
|
|
1404
|
-
});
|
|
1405
|
-
return (await createStatelessTransport(
|
|
2725
|
+
}));
|
|
2726
|
+
return withCors(await (await createStatelessTransport({
|
|
2727
|
+
transport: "http",
|
|
2728
|
+
request: request.clone(),
|
|
2729
|
+
auth
|
|
2730
|
+
})).transport.handleRequest(request, parsedBody === void 0 ? void 0 : { parsedBody }));
|
|
1406
2731
|
}
|
|
1407
2732
|
return {
|
|
1408
2733
|
GET: async ({ request }) => handle(request),
|
|
1409
2734
|
POST: async ({ request }) => handle(request),
|
|
1410
|
-
DELETE: async ({ request }) => handle(request)
|
|
2735
|
+
DELETE: async ({ request }) => handle(request),
|
|
2736
|
+
OPTIONS: async ({ request }) => handle(request)
|
|
1411
2737
|
};
|
|
1412
2738
|
}
|
|
1413
2739
|
async function runDocsMcpStdio(options) {
|
|
1414
|
-
const server = await createDocsMcpServer(
|
|
2740
|
+
const server = await createDocsMcpServer({
|
|
2741
|
+
...options,
|
|
2742
|
+
requestContext: { transport: "stdio" }
|
|
2743
|
+
});
|
|
1415
2744
|
const transport = new StdioServerTransport();
|
|
1416
2745
|
await server.connect(transport);
|
|
1417
2746
|
}
|
|
2747
|
+
async function isDocsMcpOriginAllowed(request, allowedOrigins) {
|
|
2748
|
+
const origin = request.headers.get("origin");
|
|
2749
|
+
if (!origin) return true;
|
|
2750
|
+
if (typeof allowedOrigins === "function") return allowedOrigins({
|
|
2751
|
+
origin,
|
|
2752
|
+
request
|
|
2753
|
+
});
|
|
2754
|
+
const normalizedOrigin = normalizeHttpOrigin(origin);
|
|
2755
|
+
if (allowedOrigins === "same-origin") return normalizedOrigin === new URL(request.url).origin;
|
|
2756
|
+
return allowedOrigins.some((allowedOrigin) => normalizeHttpOrigin(allowedOrigin) === normalizedOrigin);
|
|
2757
|
+
}
|
|
2758
|
+
function normalizeHttpOrigin(value) {
|
|
2759
|
+
try {
|
|
2760
|
+
return new URL(value).origin;
|
|
2761
|
+
} catch {
|
|
2762
|
+
return value.trim();
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
function isDocsMcpAuthPrincipal(value) {
|
|
2766
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
2767
|
+
const principal = value;
|
|
2768
|
+
if (typeof principal.id !== "string" || principal.id.trim().length === 0) return false;
|
|
2769
|
+
if (principal.scopes !== void 0 && (!Array.isArray(principal.scopes) || principal.scopes.some((scope) => typeof scope !== "string"))) return false;
|
|
2770
|
+
return principal.claims === void 0 || typeof principal.claims === "object" && principal.claims !== null && !Array.isArray(principal.claims);
|
|
2771
|
+
}
|
|
2772
|
+
async function prepareDocsMcpHttpRequest(request, maxBodyBytes) {
|
|
2773
|
+
if (request.method.toUpperCase() !== "POST") return {
|
|
2774
|
+
status: "ok",
|
|
2775
|
+
request,
|
|
2776
|
+
bodyParseFailed: false
|
|
2777
|
+
};
|
|
2778
|
+
if (isContentLengthOverLimit(request, maxBodyBytes)) return { status: "too-large" };
|
|
2779
|
+
const body = request.body;
|
|
2780
|
+
if (!body) return {
|
|
2781
|
+
status: "ok",
|
|
2782
|
+
request,
|
|
2783
|
+
bodyParseFailed: true
|
|
2784
|
+
};
|
|
2785
|
+
const reader = body.getReader();
|
|
2786
|
+
const chunks = [];
|
|
2787
|
+
let byteLength = 0;
|
|
2788
|
+
while (true) {
|
|
2789
|
+
const { done, value } = await reader.read();
|
|
2790
|
+
if (done) break;
|
|
2791
|
+
byteLength += value.byteLength;
|
|
2792
|
+
if (byteLength > maxBodyBytes) {
|
|
2793
|
+
reader.cancel();
|
|
2794
|
+
return { status: "too-large" };
|
|
2795
|
+
}
|
|
2796
|
+
chunks.push(value);
|
|
2797
|
+
}
|
|
2798
|
+
const bodyBytes = new Uint8Array(byteLength);
|
|
2799
|
+
let offset = 0;
|
|
2800
|
+
for (const chunk of chunks) {
|
|
2801
|
+
bodyBytes.set(chunk, offset);
|
|
2802
|
+
offset += chunk.byteLength;
|
|
2803
|
+
}
|
|
2804
|
+
const boundedRequest = new Request(request.url, {
|
|
2805
|
+
method: request.method,
|
|
2806
|
+
headers: new Headers(request.headers),
|
|
2807
|
+
body: bodyBytes,
|
|
2808
|
+
redirect: request.redirect,
|
|
2809
|
+
signal: request.signal
|
|
2810
|
+
});
|
|
2811
|
+
try {
|
|
2812
|
+
return {
|
|
2813
|
+
status: "ok",
|
|
2814
|
+
request: boundedRequest,
|
|
2815
|
+
parsedBody: JSON.parse(new TextDecoder().decode(bodyBytes)),
|
|
2816
|
+
bodyParseFailed: false
|
|
2817
|
+
};
|
|
2818
|
+
} catch {
|
|
2819
|
+
return {
|
|
2820
|
+
status: "ok",
|
|
2821
|
+
request: boundedRequest,
|
|
2822
|
+
bodyParseFailed: true
|
|
2823
|
+
};
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
function isContentLengthOverLimit(request, maxBodyBytes) {
|
|
2827
|
+
const rawContentLength = request.headers.get("content-length");
|
|
2828
|
+
if (rawContentLength === null) return false;
|
|
2829
|
+
const contentLength = Number(rawContentLength);
|
|
2830
|
+
return Number.isFinite(contentLength) && contentLength > maxBodyBytes;
|
|
2831
|
+
}
|
|
2832
|
+
const DOCS_MCP_CORS_METHODS = [
|
|
2833
|
+
"GET",
|
|
2834
|
+
"POST",
|
|
2835
|
+
"DELETE",
|
|
2836
|
+
"OPTIONS"
|
|
2837
|
+
];
|
|
2838
|
+
const DOCS_MCP_CORS_REQUEST_METHODS = new Set([
|
|
2839
|
+
"GET",
|
|
2840
|
+
"POST",
|
|
2841
|
+
"DELETE"
|
|
2842
|
+
]);
|
|
2843
|
+
function createDocsMcpOptionsResponse(request, cors) {
|
|
2844
|
+
const allow = DOCS_MCP_CORS_METHODS.join(", ");
|
|
2845
|
+
if (!request.headers.get("origin") || !cors.enabled) return new Response(null, {
|
|
2846
|
+
status: 204,
|
|
2847
|
+
headers: { Allow: allow }
|
|
2848
|
+
});
|
|
2849
|
+
const requestedMethod = request.headers.get("access-control-request-method")?.trim().toUpperCase();
|
|
2850
|
+
if (requestedMethod && !DOCS_MCP_CORS_REQUEST_METHODS.has(requestedMethod)) return applyDocsMcpPreflightCorsHeaders(createMcpHttpSecurityErrorResponse(405, "CORS request method is not allowed"), request, cors);
|
|
2851
|
+
const allowedHeaders = new Set(cors.allowedHeaders.map((header) => header.toLowerCase()));
|
|
2852
|
+
const rejectedHeader = parseCorsRequestedHeaders(request.headers.get("access-control-request-headers")).find((header) => !allowedHeaders.has(header.toLowerCase()));
|
|
2853
|
+
if (rejectedHeader) return applyDocsMcpPreflightCorsHeaders(createMcpHttpSecurityErrorResponse(403, `CORS request header is not allowed: ${rejectedHeader}`), request, cors);
|
|
2854
|
+
return applyDocsMcpPreflightCorsHeaders(new Response(null, {
|
|
2855
|
+
status: 204,
|
|
2856
|
+
headers: { Allow: allow }
|
|
2857
|
+
}), request, cors);
|
|
2858
|
+
}
|
|
2859
|
+
function parseCorsRequestedHeaders(value) {
|
|
2860
|
+
if (!value) return [];
|
|
2861
|
+
return value.split(",").map((header) => header.trim()).filter(Boolean);
|
|
2862
|
+
}
|
|
2863
|
+
function applyDocsMcpPreflightCorsHeaders(response, request, cors) {
|
|
2864
|
+
const headers = buildDocsMcpCorsHeaders(response.headers, request, cors);
|
|
2865
|
+
headers.set("Access-Control-Allow-Methods", DOCS_MCP_CORS_METHODS.join(", "));
|
|
2866
|
+
headers.set("Access-Control-Allow-Headers", cors.allowedHeaders.join(", "));
|
|
2867
|
+
headers.set("Access-Control-Max-Age", String(cors.maxAgeSeconds));
|
|
2868
|
+
appendVaryHeader(headers, "Access-Control-Request-Method");
|
|
2869
|
+
appendVaryHeader(headers, "Access-Control-Request-Headers");
|
|
2870
|
+
return cloneResponseWithHeaders(response, headers);
|
|
2871
|
+
}
|
|
2872
|
+
function applyDocsMcpCorsHeaders(response, request, cors) {
|
|
2873
|
+
if (!request.headers.has("origin") || !cors.enabled) return response;
|
|
2874
|
+
const headers = buildDocsMcpCorsHeaders(response.headers, request, cors);
|
|
2875
|
+
if (!headers.has("Access-Control-Allow-Origin")) return response;
|
|
2876
|
+
if (cors.exposedHeaders.length > 0) headers.set("Access-Control-Expose-Headers", cors.exposedHeaders.join(", "));
|
|
2877
|
+
return cloneResponseWithHeaders(response, headers);
|
|
2878
|
+
}
|
|
2879
|
+
function buildDocsMcpCorsHeaders(source, request, cors) {
|
|
2880
|
+
const headers = new Headers(source);
|
|
2881
|
+
headers.delete("Access-Control-Allow-Origin");
|
|
2882
|
+
headers.delete("Access-Control-Allow-Credentials");
|
|
2883
|
+
const origin = serializeCorsOrigin(request.headers.get("origin"));
|
|
2884
|
+
if (!origin) return headers;
|
|
2885
|
+
headers.set("Access-Control-Allow-Origin", origin);
|
|
2886
|
+
if (cors.allowCredentials) headers.set("Access-Control-Allow-Credentials", "true");
|
|
2887
|
+
else headers.delete("Access-Control-Allow-Credentials");
|
|
2888
|
+
appendVaryHeader(headers, "Origin");
|
|
2889
|
+
return headers;
|
|
2890
|
+
}
|
|
2891
|
+
function serializeCorsOrigin(value) {
|
|
2892
|
+
if (!value) return null;
|
|
2893
|
+
if (value.trim() === "null") return "null";
|
|
2894
|
+
try {
|
|
2895
|
+
const origin = new URL(value).origin;
|
|
2896
|
+
return origin === "null" ? null : origin;
|
|
2897
|
+
} catch {
|
|
2898
|
+
return null;
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
function appendVaryHeader(headers, value) {
|
|
2902
|
+
const values = (headers.get("Vary") ?? "").split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
2903
|
+
if (!values.some((entry) => entry.toLowerCase() === value.toLowerCase())) values.push(value);
|
|
2904
|
+
headers.set("Vary", values.join(", "));
|
|
2905
|
+
}
|
|
2906
|
+
function cloneResponseWithHeaders(response, headers) {
|
|
2907
|
+
return new Response(response.body, {
|
|
2908
|
+
status: response.status,
|
|
2909
|
+
statusText: response.statusText,
|
|
2910
|
+
headers
|
|
2911
|
+
});
|
|
2912
|
+
}
|
|
2913
|
+
function createMcpRequestTooLargeResponse(maxBodyBytes) {
|
|
2914
|
+
const response = createJsonRpcErrorResponse({
|
|
2915
|
+
status: 413,
|
|
2916
|
+
code: -32e3,
|
|
2917
|
+
message: `Request body exceeds the ${maxBodyBytes} byte limit`,
|
|
2918
|
+
data: {
|
|
2919
|
+
reason: "request_too_large",
|
|
2920
|
+
maxBodyBytes
|
|
2921
|
+
}
|
|
2922
|
+
});
|
|
2923
|
+
response.headers.set("Cache-Control", "no-store");
|
|
2924
|
+
return response;
|
|
2925
|
+
}
|
|
2926
|
+
function createMcpHttpSecurityErrorResponse(status, error) {
|
|
2927
|
+
return new Response(JSON.stringify({ error }), {
|
|
2928
|
+
status,
|
|
2929
|
+
headers: {
|
|
2930
|
+
"Content-Type": "application/json",
|
|
2931
|
+
"Cache-Control": "no-store"
|
|
2932
|
+
}
|
|
2933
|
+
});
|
|
2934
|
+
}
|
|
1418
2935
|
function createJsonErrorResponse(status, error) {
|
|
1419
2936
|
return new Response(JSON.stringify({ error }), {
|
|
1420
2937
|
status,
|
|
@@ -1570,9 +3087,14 @@ function scanFilesystemDocsPages(contentDirAbs, entry, rootDir) {
|
|
|
1570
3087
|
title,
|
|
1571
3088
|
description: data.description,
|
|
1572
3089
|
relatedInput: data.related,
|
|
3090
|
+
agent: normalizePageAgentFrontmatter(data.agent),
|
|
1573
3091
|
icon: data.icon,
|
|
1574
3092
|
sourcePath: path.relative(rootDir, full).replace(/\\/g, "/"),
|
|
1575
3093
|
lastModified: stat.mtime.toISOString(),
|
|
3094
|
+
locale: typeof data.locale === "string" ? data.locale : void 0,
|
|
3095
|
+
framework: typeof data.framework === "string" ? data.framework : void 0,
|
|
3096
|
+
version: typeof data.version === "string" ? data.version : void 0,
|
|
3097
|
+
tags: Array.isArray(data.tags) ? data.tags.filter((tag) => typeof tag === "string") : void 0,
|
|
1576
3098
|
content: stripMarkdownForMcp(humanRawContent),
|
|
1577
3099
|
rawContent: humanRawContent,
|
|
1578
3100
|
agentFallbackContent: pageAgentContent,
|
|
@@ -1714,15 +3236,20 @@ function toSearchSourcePages(pages) {
|
|
|
1714
3236
|
rawContent: page.agentRawContent ?? page.agentFallbackRawContent ?? page.rawContent,
|
|
1715
3237
|
sourcePath: page.sourcePath,
|
|
1716
3238
|
lastModified: page.lastModified,
|
|
3239
|
+
locale: page.locale,
|
|
3240
|
+
framework: page.framework,
|
|
3241
|
+
version: page.version,
|
|
3242
|
+
tags: page.tags,
|
|
1717
3243
|
agentContent: page.agentContent,
|
|
1718
3244
|
agentRawContent: page.agentRawContent,
|
|
1719
3245
|
agentFallbackContent: page.agentFallbackContent,
|
|
1720
3246
|
agentFallbackRawContent: page.agentFallbackRawContent,
|
|
1721
3247
|
description: page.description,
|
|
1722
|
-
related: page.related
|
|
3248
|
+
related: page.related,
|
|
3249
|
+
agent: page.agent
|
|
1723
3250
|
}));
|
|
1724
3251
|
}
|
|
1725
|
-
function getDocsConfigSchema(filters) {
|
|
3252
|
+
function getDocsConfigSchema(filters = {}) {
|
|
1726
3253
|
const option = filters.option?.trim();
|
|
1727
3254
|
const query = filters.query?.trim();
|
|
1728
3255
|
let options = DOCS_CONFIG_SCHEMA_OPTIONS.map(cloneConfigSchemaOption);
|
|
@@ -1738,12 +3265,13 @@ function getDocsConfigSchema(filters) {
|
|
|
1738
3265
|
} : void 0,
|
|
1739
3266
|
resultCount: countConfigSchemaOptions(options),
|
|
1740
3267
|
options,
|
|
1741
|
-
examples: DOCS_CONFIG_SCHEMA_EXAMPLES
|
|
3268
|
+
examples: DOCS_CONFIG_SCHEMA_EXAMPLES.map((example) => ({ ...example }))
|
|
1742
3269
|
};
|
|
1743
3270
|
}
|
|
1744
3271
|
function cloneConfigSchemaOption(option) {
|
|
1745
3272
|
return {
|
|
1746
3273
|
...option,
|
|
3274
|
+
values: option.values ? [...option.values] : void 0,
|
|
1747
3275
|
children: option.children?.map(cloneConfigSchemaOption)
|
|
1748
3276
|
};
|
|
1749
3277
|
}
|
|
@@ -1803,13 +3331,24 @@ function resolveMcpToolSearchConfig(search, route) {
|
|
|
1803
3331
|
chunking: config.chunking
|
|
1804
3332
|
};
|
|
1805
3333
|
}
|
|
3334
|
+
function toAgentContractSummary(value) {
|
|
3335
|
+
const agent = normalizePageAgentFrontmatter(value);
|
|
3336
|
+
const hasContract = hasStructuredPageAgentContract(agent);
|
|
3337
|
+
return {
|
|
3338
|
+
hasContract,
|
|
3339
|
+
...hasContract && agent?.task ? { task: agent.task } : {},
|
|
3340
|
+
...hasContract && agent?.outcome ? { outcome: agent.outcome } : {},
|
|
3341
|
+
...hasContract && agent?.appliesTo ? { appliesTo: agent.appliesTo } : {}
|
|
3342
|
+
};
|
|
3343
|
+
}
|
|
1806
3344
|
function toPageSummaries(pages) {
|
|
1807
3345
|
return pages.map((page) => ({
|
|
1808
3346
|
slug: page.slug,
|
|
1809
3347
|
url: page.url,
|
|
1810
3348
|
title: page.title,
|
|
1811
3349
|
description: page.description,
|
|
1812
|
-
icon: page.icon
|
|
3350
|
+
icon: page.icon,
|
|
3351
|
+
agent: toAgentContractSummary(page.agent)
|
|
1813
3352
|
}));
|
|
1814
3353
|
}
|
|
1815
3354
|
function toDocsListPageSummary(page) {
|
|
@@ -1818,11 +3357,47 @@ function toDocsListPageSummary(page) {
|
|
|
1818
3357
|
url: page.url,
|
|
1819
3358
|
title: page.title,
|
|
1820
3359
|
description: page.description,
|
|
3360
|
+
agent: toAgentContractSummary(page.agent),
|
|
1821
3361
|
icon: page.icon,
|
|
1822
3362
|
sourcePath: page.sourcePath,
|
|
1823
3363
|
lastModified: page.lastModified
|
|
1824
3364
|
};
|
|
1825
3365
|
}
|
|
3366
|
+
function listDocsTasks(pages, filters) {
|
|
3367
|
+
const query = filters.query?.toLowerCase();
|
|
3368
|
+
const applicabilityFilters = [
|
|
3369
|
+
["framework", filters.framework],
|
|
3370
|
+
["version", filters.version],
|
|
3371
|
+
["package", filters.package]
|
|
3372
|
+
];
|
|
3373
|
+
return pages.flatMap((page) => {
|
|
3374
|
+
const agent = normalizePageAgentFrontmatter(page.agent);
|
|
3375
|
+
if (!agent || !hasStructuredPageAgentContract(agent)) return [];
|
|
3376
|
+
for (const [field, expected] of applicabilityFilters) {
|
|
3377
|
+
if (!expected) continue;
|
|
3378
|
+
const actualValue = agent.appliesTo?.[field];
|
|
3379
|
+
if (!(typeof actualValue === "string" ? [actualValue] : actualValue ?? []).some((value) => value.toLowerCase() === expected.toLowerCase())) return [];
|
|
3380
|
+
}
|
|
3381
|
+
if (query) {
|
|
3382
|
+
if (![
|
|
3383
|
+
page.slug,
|
|
3384
|
+
page.url,
|
|
3385
|
+
page.title,
|
|
3386
|
+
page.description,
|
|
3387
|
+
JSON.stringify(agent)
|
|
3388
|
+
].filter(Boolean).join("\n").toLowerCase().includes(query)) return [];
|
|
3389
|
+
}
|
|
3390
|
+
return [{
|
|
3391
|
+
slug: page.slug,
|
|
3392
|
+
url: page.url,
|
|
3393
|
+
title: page.title,
|
|
3394
|
+
...page.description ? { description: page.description } : {},
|
|
3395
|
+
...agent.task ? { task: agent.task } : {},
|
|
3396
|
+
...agent.outcome ? { outcome: agent.outcome } : {},
|
|
3397
|
+
...agent.appliesTo ? { appliesTo: agent.appliesTo } : {}
|
|
3398
|
+
}];
|
|
3399
|
+
});
|
|
3400
|
+
}
|
|
1826
3401
|
function listDocsBySection(pages, filters) {
|
|
1827
3402
|
const allPages = pages.map(toDocsListPageSummary);
|
|
1828
3403
|
const tree = buildDocsSectionTree(pages);
|
|
@@ -2113,6 +3688,288 @@ function getCodeExampleSearchText(example) {
|
|
|
2113
3688
|
example.code
|
|
2114
3689
|
].filter((value) => typeof value === "string").join("\n");
|
|
2115
3690
|
}
|
|
3691
|
+
function limitDocsMcpText(value, maxChars) {
|
|
3692
|
+
if (maxChars === void 0 || value.length <= maxChars) return {
|
|
3693
|
+
text: value,
|
|
3694
|
+
truncated: false
|
|
3695
|
+
};
|
|
3696
|
+
if (maxChars <= 1) return {
|
|
3697
|
+
text: value.slice(0, maxChars),
|
|
3698
|
+
truncated: true
|
|
3699
|
+
};
|
|
3700
|
+
const suffix = "…";
|
|
3701
|
+
const available = Math.max(0, maxChars - 1);
|
|
3702
|
+
let selected = value.slice(0, available);
|
|
3703
|
+
const paragraphBreak = selected.lastIndexOf("\n\n");
|
|
3704
|
+
const lineBreak = selected.lastIndexOf("\n");
|
|
3705
|
+
const preferredBreak = paragraphBreak >= available * .6 ? paragraphBreak : lineBreak;
|
|
3706
|
+
if (preferredBreak >= available * .6) selected = selected.slice(0, preferredBreak);
|
|
3707
|
+
return {
|
|
3708
|
+
text: `${selected.trimEnd()}${suffix}`.slice(0, maxChars),
|
|
3709
|
+
truncated: true
|
|
3710
|
+
};
|
|
3711
|
+
}
|
|
3712
|
+
function docsMcpUtf8Bytes(value) {
|
|
3713
|
+
return UTF8_ENCODER.encode(value).byteLength;
|
|
3714
|
+
}
|
|
3715
|
+
function limitDocsMcpUtf8Bytes(value, maxUtf8Bytes) {
|
|
3716
|
+
if (docsMcpUtf8Bytes(value) <= maxUtf8Bytes) return {
|
|
3717
|
+
text: value,
|
|
3718
|
+
truncated: false
|
|
3719
|
+
};
|
|
3720
|
+
if (maxUtf8Bytes <= 0) return {
|
|
3721
|
+
text: "",
|
|
3722
|
+
truncated: true
|
|
3723
|
+
};
|
|
3724
|
+
const suffix = "…";
|
|
3725
|
+
const suffixBytes = docsMcpUtf8Bytes(suffix);
|
|
3726
|
+
const availableBytes = Math.max(0, maxUtf8Bytes - suffixBytes);
|
|
3727
|
+
let selected = "";
|
|
3728
|
+
let selectedBytes = 0;
|
|
3729
|
+
for (const character of value) {
|
|
3730
|
+
const characterBytes = docsMcpUtf8Bytes(character);
|
|
3731
|
+
if (selectedBytes + characterBytes > availableBytes) break;
|
|
3732
|
+
selected += character;
|
|
3733
|
+
selectedBytes += characterBytes;
|
|
3734
|
+
}
|
|
3735
|
+
const paragraphBreak = selected.lastIndexOf("\n\n");
|
|
3736
|
+
const lineBreak = selected.lastIndexOf("\n");
|
|
3737
|
+
const preferredBreak = paragraphBreak >= selected.length * .6 ? paragraphBreak : lineBreak;
|
|
3738
|
+
if (preferredBreak >= selected.length * .6) selected = selected.slice(0, preferredBreak);
|
|
3739
|
+
if (suffixBytes > maxUtf8Bytes) return {
|
|
3740
|
+
text: ".".repeat(maxUtf8Bytes),
|
|
3741
|
+
truncated: true
|
|
3742
|
+
};
|
|
3743
|
+
return {
|
|
3744
|
+
text: `${selected.trimEnd()}${suffix}`,
|
|
3745
|
+
truncated: true
|
|
3746
|
+
};
|
|
3747
|
+
}
|
|
3748
|
+
function renderBoundedDocsMcpSectionError(options) {
|
|
3749
|
+
const maxChars = options.maxChars ?? Number.POSITIVE_INFINITY;
|
|
3750
|
+
const shorten = (value, max) => limitDocsMcpText(value, max).text;
|
|
3751
|
+
const availableSections = [];
|
|
3752
|
+
const base = {
|
|
3753
|
+
error: "section_not_found",
|
|
3754
|
+
message: `No section matched "${shorten(options.requestedSection, 80)}" in "${shorten(options.pageUrl, 80)}".`,
|
|
3755
|
+
availableSections,
|
|
3756
|
+
truncated: false
|
|
3757
|
+
};
|
|
3758
|
+
const serialize = () => JSON.stringify(base, null, 2);
|
|
3759
|
+
for (const section of options.availableSections) {
|
|
3760
|
+
availableSections.push({
|
|
3761
|
+
title: shorten(section.title, 80),
|
|
3762
|
+
anchor: shorten(section.anchor, 80)
|
|
3763
|
+
});
|
|
3764
|
+
if (serialize().length > maxChars) {
|
|
3765
|
+
availableSections.pop();
|
|
3766
|
+
base.truncated = true;
|
|
3767
|
+
break;
|
|
3768
|
+
}
|
|
3769
|
+
}
|
|
3770
|
+
if (availableSections.length < options.availableSections.length) base.truncated = true;
|
|
3771
|
+
if (serialize().length <= maxChars) return serialize();
|
|
3772
|
+
const minimalText = JSON.stringify({
|
|
3773
|
+
error: "section_not_found",
|
|
3774
|
+
message: "The requested section was not found.",
|
|
3775
|
+
availableSections: [],
|
|
3776
|
+
truncated: true
|
|
3777
|
+
});
|
|
3778
|
+
if (minimalText.length <= maxChars) return minimalText;
|
|
3779
|
+
return JSON.stringify({ error: "section_not_found" });
|
|
3780
|
+
}
|
|
3781
|
+
function toStructuredDocsMcpPage(page) {
|
|
3782
|
+
return {
|
|
3783
|
+
slug: page.slug,
|
|
3784
|
+
url: page.url,
|
|
3785
|
+
title: page.title,
|
|
3786
|
+
description: page.description,
|
|
3787
|
+
related: page.related,
|
|
3788
|
+
icon: page.icon,
|
|
3789
|
+
sourcePath: page.sourcePath,
|
|
3790
|
+
lastModified: page.lastModified,
|
|
3791
|
+
locale: page.locale,
|
|
3792
|
+
framework: page.framework,
|
|
3793
|
+
version: page.version,
|
|
3794
|
+
tags: page.tags
|
|
3795
|
+
};
|
|
3796
|
+
}
|
|
3797
|
+
function getDocsMcpSourceMarkdown(page) {
|
|
3798
|
+
return page.agentRawContent ?? page.agentFallbackRawContent ?? page.rawContent ?? page.agentContent ?? page.agentFallbackContent ?? page.content;
|
|
3799
|
+
}
|
|
3800
|
+
function resolveDocsMcpScopeField(pageValue, contractValue, filter, overlaps, matchesFilter) {
|
|
3801
|
+
const pageValues = normalizeAgentScopeValues(pageValue);
|
|
3802
|
+
const contractValues = normalizeAgentScopeValues(contractValue);
|
|
3803
|
+
const conflict = pageValues.length > 0 && contractValues.length > 0 && !pageValues.some((topLevel) => contractValues.some((contract) => overlaps(topLevel, contract)));
|
|
3804
|
+
const pageMatches = !filter || pageValues.length === 0 || pageValues.some((candidate) => matchesFilter(filter, candidate));
|
|
3805
|
+
const contractMatches = !filter || contractValues.length === 0 || contractValues.some((candidate) => matchesFilter(filter, candidate));
|
|
3806
|
+
const matchedContract = filter ? contractValues.find((candidate) => matchesFilter(filter, candidate)) : void 0;
|
|
3807
|
+
return {
|
|
3808
|
+
conflict,
|
|
3809
|
+
matches: !conflict && pageMatches && contractMatches,
|
|
3810
|
+
value: pageValues[0] ?? matchedContract ?? (contractValues.length === 1 ? contractValues[0] : void 0)
|
|
3811
|
+
};
|
|
3812
|
+
}
|
|
3813
|
+
function resolveDocsMcpEffectiveScope(page, filters) {
|
|
3814
|
+
const framework = resolveDocsMcpScopeField(page.framework, page.agent?.appliesTo?.framework, filters.framework, (left, right) => normalizeAgentFramework(left) === normalizeAgentFramework(right), (filter, candidate) => normalizeAgentFramework(filter) === normalizeAgentFramework(candidate));
|
|
3815
|
+
const version = resolveDocsMcpScopeField(page.version, page.agent?.appliesTo?.version, filters.version, agentVersionConstraintsOverlap, agentVersionConstraintMatches);
|
|
3816
|
+
const locale = page.locale?.trim() || void 0;
|
|
3817
|
+
const localeMatches = !filters.locale || !locale || normalizeAgentLocale(locale) === normalizeAgentLocale(filters.locale);
|
|
3818
|
+
return {
|
|
3819
|
+
framework: framework.value,
|
|
3820
|
+
version: version.value,
|
|
3821
|
+
locale,
|
|
3822
|
+
conflict: framework.conflict || version.conflict,
|
|
3823
|
+
matches: framework.matches && version.matches && localeMatches
|
|
3824
|
+
};
|
|
3825
|
+
}
|
|
3826
|
+
function getDocsMcpResultPageUrl(value) {
|
|
3827
|
+
return value.split("#", 1)[0] ?? value;
|
|
3828
|
+
}
|
|
3829
|
+
function getDocsMcpResultAnchor(value) {
|
|
3830
|
+
const anchor = value.split("#", 2)[1];
|
|
3831
|
+
if (!anchor) return void 0;
|
|
3832
|
+
try {
|
|
3833
|
+
return decodeURIComponent(anchor);
|
|
3834
|
+
} catch {
|
|
3835
|
+
return anchor;
|
|
3836
|
+
}
|
|
3837
|
+
}
|
|
3838
|
+
async function buildDocsMcpContext(options) {
|
|
3839
|
+
const scopedPageEntries = options.pages.flatMap((page) => {
|
|
3840
|
+
const scope = resolveDocsMcpEffectiveScope(page, {
|
|
3841
|
+
framework: options.framework,
|
|
3842
|
+
version: options.version,
|
|
3843
|
+
locale: options.locale
|
|
3844
|
+
});
|
|
3845
|
+
return scope.matches && !scope.conflict ? [{
|
|
3846
|
+
page,
|
|
3847
|
+
scope
|
|
3848
|
+
}] : [];
|
|
3849
|
+
});
|
|
3850
|
+
const scopedPages = scopedPageEntries.map(({ page }) => page);
|
|
3851
|
+
const scopeByPage = new Map(scopedPageEntries.map(({ page, scope }) => [page, scope]));
|
|
3852
|
+
const maxResults = typeof options.maxResults === "number" && Number.isFinite(options.maxResults) ? Math.max(1, Math.min(50, Math.floor(options.maxResults))) : 50;
|
|
3853
|
+
const orderedResults = [...await performDocsSearch({
|
|
3854
|
+
pages: toSearchSourcePages(scopedPages),
|
|
3855
|
+
query: options.query,
|
|
3856
|
+
search: {
|
|
3857
|
+
enabled: true,
|
|
3858
|
+
provider: "simple",
|
|
3859
|
+
maxResults: 50,
|
|
3860
|
+
chunking: { strategy: "section" }
|
|
3861
|
+
},
|
|
3862
|
+
locale: options.locale,
|
|
3863
|
+
siteTitle: options.siteTitle,
|
|
3864
|
+
limit: 50
|
|
3865
|
+
})].sort((left, right) => {
|
|
3866
|
+
const scoreDelta = (right.score ?? 0) - (left.score ?? 0);
|
|
3867
|
+
if (scoreDelta !== 0) return scoreDelta;
|
|
3868
|
+
if (left.url !== right.url) return left.url < right.url ? -1 : 1;
|
|
3869
|
+
if ((left.section ?? "") !== (right.section ?? "")) return (left.section ?? "") < (right.section ?? "") ? -1 : 1;
|
|
3870
|
+
if (left.id === right.id) return 0;
|
|
3871
|
+
return left.id < right.id ? -1 : 1;
|
|
3872
|
+
});
|
|
3873
|
+
const sectionPageUrls = new Set(orderedResults.filter((result) => result.section).map((result) => normalizeUrlPath(getDocsMcpResultPageUrl(result.url))));
|
|
3874
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3875
|
+
const resolvedCandidates = orderedResults.filter((result) => {
|
|
3876
|
+
const pageUrl = normalizeUrlPath(getDocsMcpResultPageUrl(result.url));
|
|
3877
|
+
if (!result.section && sectionPageUrls.has(pageUrl)) return false;
|
|
3878
|
+
const key = `${pageUrl}#${getDocsMcpResultAnchor(result.url) ?? result.section ?? ""}`;
|
|
3879
|
+
if (seen.has(key)) return false;
|
|
3880
|
+
seen.add(key);
|
|
3881
|
+
return true;
|
|
3882
|
+
}).flatMap((result) => {
|
|
3883
|
+
const page = findDocsPage(scopedPages, getDocsMcpResultPageUrl(result.url), options.entry);
|
|
3884
|
+
if (!page) return [];
|
|
3885
|
+
const scope = scopeByPage.get(page);
|
|
3886
|
+
if (!scope) return [];
|
|
3887
|
+
const document = upsertPageAgentContractMarkdown(getDocsMcpSourceMarkdown(page), page.agent);
|
|
3888
|
+
const resultAnchor = getDocsMcpResultAnchor(result.url);
|
|
3889
|
+
const selectedSection = resultAnchor ? findDocsMarkdownSection(document, resultAnchor) : result.section ? findDocsMarkdownSection(document, result.section) : void 0;
|
|
3890
|
+
if ((resultAnchor || result.section) && !selectedSection) return [];
|
|
3891
|
+
const rawContent = (selectedSection?.content ?? document).trim();
|
|
3892
|
+
if (!rawContent) return [];
|
|
3893
|
+
return [{
|
|
3894
|
+
result,
|
|
3895
|
+
page,
|
|
3896
|
+
scope,
|
|
3897
|
+
selectedSection,
|
|
3898
|
+
rawContent
|
|
3899
|
+
}];
|
|
3900
|
+
}).slice(0, maxResults);
|
|
3901
|
+
const maxUtf8Bytes = options.tokenBudget;
|
|
3902
|
+
const separator = "\n\n---\n\n";
|
|
3903
|
+
const separatorUtf8Bytes = docsMcpUtf8Bytes(separator);
|
|
3904
|
+
const blocks = [];
|
|
3905
|
+
const sources = [];
|
|
3906
|
+
let usedUtf8Bytes = 0;
|
|
3907
|
+
for (const { result, page, scope, selectedSection, rawContent } of resolvedCandidates) {
|
|
3908
|
+
const anchor = selectedSection?.anchor;
|
|
3909
|
+
const sourceUrl = anchor ? `${page.url}#${anchor}` : page.url;
|
|
3910
|
+
const headerLines = [`## ${page.title}`, `Source: ${sourceUrl}`];
|
|
3911
|
+
if (selectedSection?.title) headerLines.push(`Section: ${selectedSection.title}`);
|
|
3912
|
+
if (scope.framework) headerLines.push(`Framework: ${scope.framework}`);
|
|
3913
|
+
if (scope.version) headerLines.push(`Version: ${scope.version}`);
|
|
3914
|
+
if (scope.locale) headerLines.push(`Locale: ${scope.locale}`);
|
|
3915
|
+
const header = headerLines.join("\n");
|
|
3916
|
+
const separatorBytes = blocks.length === 0 ? 0 : separatorUtf8Bytes;
|
|
3917
|
+
const headerBytes = docsMcpUtf8Bytes(`${header}\n\n`);
|
|
3918
|
+
const availableForContent = maxUtf8Bytes - usedUtf8Bytes - separatorBytes - headerBytes;
|
|
3919
|
+
if (availableForContent <= 0) break;
|
|
3920
|
+
const limited = limitDocsMcpUtf8Bytes(rawContent, availableForContent);
|
|
3921
|
+
if (!limited.text) break;
|
|
3922
|
+
const block = `${header}\n\n${limited.text}`;
|
|
3923
|
+
const blockUtf8Bytes = docsMcpUtf8Bytes(block);
|
|
3924
|
+
blocks.push(block);
|
|
3925
|
+
usedUtf8Bytes += separatorBytes + blockUtf8Bytes;
|
|
3926
|
+
sources.push({
|
|
3927
|
+
id: result.id,
|
|
3928
|
+
title: page.title,
|
|
3929
|
+
pageUrl: page.url,
|
|
3930
|
+
url: sourceUrl,
|
|
3931
|
+
section: selectedSection?.title,
|
|
3932
|
+
anchor,
|
|
3933
|
+
sourcePath: page.sourcePath,
|
|
3934
|
+
lastModified: page.lastModified,
|
|
3935
|
+
locale: scope.locale,
|
|
3936
|
+
framework: scope.framework,
|
|
3937
|
+
version: scope.version,
|
|
3938
|
+
tags: page.tags ? [...page.tags] : void 0,
|
|
3939
|
+
score: result.score,
|
|
3940
|
+
content: limited.text,
|
|
3941
|
+
chars: limited.text.length,
|
|
3942
|
+
utf8Bytes: docsMcpUtf8Bytes(limited.text),
|
|
3943
|
+
truncated: limited.truncated
|
|
3944
|
+
});
|
|
3945
|
+
if (limited.truncated) break;
|
|
3946
|
+
}
|
|
3947
|
+
const context = blocks.join(separator);
|
|
3948
|
+
usedUtf8Bytes = docsMcpUtf8Bytes(context);
|
|
3949
|
+
const remainingUtf8Bytes = Math.max(0, maxUtf8Bytes - usedUtf8Bytes);
|
|
3950
|
+
const truncated = sources.some((source) => source.truncated) || sources.length < resolvedCandidates.length;
|
|
3951
|
+
return {
|
|
3952
|
+
query: options.query,
|
|
3953
|
+
filters: {
|
|
3954
|
+
framework: options.framework,
|
|
3955
|
+
version: options.version,
|
|
3956
|
+
locale: options.locale
|
|
3957
|
+
},
|
|
3958
|
+
budget: {
|
|
3959
|
+
requestedTokens: options.tokenBudget,
|
|
3960
|
+
strategy: "utf8-bytes",
|
|
3961
|
+
maxUtf8Bytes,
|
|
3962
|
+
usedUtf8Bytes,
|
|
3963
|
+
conservativeTokenUpperBound: usedUtf8Bytes,
|
|
3964
|
+
remainingUtf8Bytes,
|
|
3965
|
+
truncated
|
|
3966
|
+
},
|
|
3967
|
+
resultCount: sources.length,
|
|
3968
|
+
candidateCount: resolvedCandidates.length,
|
|
3969
|
+
context,
|
|
3970
|
+
sources
|
|
3971
|
+
};
|
|
3972
|
+
}
|
|
2116
3973
|
function findDocsPage(pages, requestedPath, entry) {
|
|
2117
3974
|
const normalizedRequest = normalizeRequestedPath(requestedPath, entry);
|
|
2118
3975
|
for (const page of pages) if (normalizeUrlPath(page.url) === normalizedRequest) return page;
|
|
@@ -2141,12 +3998,12 @@ function normalizeUrlPath(value) {
|
|
|
2141
3998
|
return normalized.replace(/\/+$/, "");
|
|
2142
3999
|
}
|
|
2143
4000
|
function renderPageDocument(page) {
|
|
2144
|
-
if (page.agentRawContent !== void 0) return page.agentRawContent;
|
|
4001
|
+
if (page.agentRawContent !== void 0) return upsertPageAgentContractMarkdown(page.agentRawContent, page.agent);
|
|
2145
4002
|
const relatedLines = renderDocsRelatedMarkdownLines(page.related);
|
|
2146
4003
|
const lines = [`# ${page.title}`, `URL: ${page.url}`];
|
|
2147
4004
|
if (page.description) lines.push(`Description: ${page.description}`);
|
|
2148
4005
|
lines.push(...relatedLines);
|
|
2149
|
-
lines.push("", page.agentFallbackRawContent ?? page.rawContent ?? page.content);
|
|
4006
|
+
lines.push("", upsertPageAgentContractMarkdown(page.agentFallbackRawContent ?? page.rawContent ?? page.content, page.agent));
|
|
2150
4007
|
return lines.join("\n");
|
|
2151
4008
|
}
|
|
2152
4009
|
function renderNavigationTree(tree) {
|
|
@@ -2175,4 +4032,4 @@ function toPageResourceUri(url) {
|
|
|
2175
4032
|
}
|
|
2176
4033
|
|
|
2177
4034
|
//#endregion
|
|
2178
|
-
export { createDocsMcpHttpHandler, createDocsMcpServer, createFilesystemDocsMcpSource, normalizeDocsMcpRoute, resolveDocsMcpConfig, runDocsMcpStdio };
|
|
4035
|
+
export { DEFAULT_DOCS_MCP_CORS_ALLOWED_HEADERS, DEFAULT_DOCS_MCP_CORS_EXPOSED_HEADERS, DEFAULT_DOCS_MCP_CORS_MAX_AGE_SECONDS, DEFAULT_DOCS_MCP_MAX_BODY_BYTES, DOCS_CONFIG_SCHEMA_OPTIONS, buildDocsMcpContext, createDocsMcpHttpHandler, createDocsMcpServer, createFilesystemDocsMcpSource, getDocsConfigSchema, normalizeDocsMcpRoute, resolveDocsMcpConfig, runDocsMcpStdio };
|