@farming-labs/docs 0.2.60 → 0.2.62

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.
Files changed (51) hide show
  1. package/README.md +1 -1
  2. package/package.json +11 -1
  3. package/dist/agent-BFqyqEnC.mjs +0 -4547
  4. package/dist/agent-DlxriaTs.mjs +0 -624
  5. package/dist/agent-evals-kJs2Y9xR.mjs +0 -2144
  6. package/dist/agent-export-BgUaiW8f.mjs +0 -869
  7. package/dist/agent-scope-CCaIY1aK.mjs +0 -283
  8. package/dist/agents-Djh-HXih.mjs +0 -219
  9. package/dist/analytics-Bx44lg6d.mjs +0 -177
  10. package/dist/cli/index.d.mts +0 -15
  11. package/dist/cli/index.mjs +0 -452
  12. package/dist/client/react.d.mts +0 -45
  13. package/dist/client/react.mjs +0 -223
  14. package/dist/cloud-BH_sHX64.mjs +0 -1615
  15. package/dist/cloud-analytics-CSyFE6SS.mjs +0 -132
  16. package/dist/cloud-ask-ai-B2WnG4fF.d.mts +0 -23
  17. package/dist/cloud-ask-ai-hnJfj8-X.mjs +0 -382
  18. package/dist/code-blocks-qe0T8-xe.mjs +0 -871
  19. package/dist/codeblocks-Bq67u32v.mjs +0 -250
  20. package/dist/config-DASewQ0x.mjs +0 -363
  21. package/dist/dev-DgY5xGl9.mjs +0 -1333
  22. package/dist/docs-cloud-server.d.mts +0 -70
  23. package/dist/docs-cloud-server.mjs +0 -310
  24. package/dist/doctor-CO1VMcF_.mjs +0 -1906
  25. package/dist/downgrade-BZs86NVr.mjs +0 -184
  26. package/dist/errors-CVqZ3kOO.mjs +0 -20
  27. package/dist/golden-evaluations-BN9u2wxw.mjs +0 -1483
  28. package/dist/i18n-CAlj1ADU.mjs +0 -40
  29. package/dist/index.d.mts +0 -1099
  30. package/dist/index.mjs +0 -9
  31. package/dist/init-Bd_k06bR.mjs +0 -1233
  32. package/dist/mcp-B_yXL5G5.mjs +0 -137
  33. package/dist/mcp.d.mts +0 -287
  34. package/dist/mcp.mjs +0 -4135
  35. package/dist/metadata-BDuewuzq.mjs +0 -237
  36. package/dist/package-version-qik_4J6C.mjs +0 -128
  37. package/dist/reading-time-BkEft6SD.mjs +0 -741
  38. package/dist/review-NC-sOdXn.mjs +0 -665
  39. package/dist/robots-DskPvGPw.mjs +0 -178
  40. package/dist/robots-ltltiLJF.mjs +0 -197
  41. package/dist/search-C1JitPwi.d.mts +0 -397
  42. package/dist/search-D57JXQLj.mjs +0 -1758
  43. package/dist/search-o4Ud6OXv.mjs +0 -102
  44. package/dist/server.d.mts +0 -341
  45. package/dist/server.mjs +0 -11
  46. package/dist/sitemap-Cq-Yj_iA.mjs +0 -247
  47. package/dist/sitemap-server-C1ibVKOy.mjs +0 -1137
  48. package/dist/templates-DNw15P-x.mjs +0 -2373
  49. package/dist/types-XHABMh_f.d.mts +0 -3248
  50. package/dist/upgrade-BCJTCW3O.mjs +0 -56
  51. package/dist/utils-6UCLxv4B.mjs +0 -225
package/dist/mcp.mjs DELETED
@@ -1,4135 +0,0 @@
1
- import { a as emitDocsAnalyticsEvent, i as emitDocsAgentTraceEvent, n as createDocsAgentTraceContext, r as createDocsAgentTraceId } from "./analytics-Bx44lg6d.mjs";
2
- import { Ot as resolveDocsAudienceMdxContent, Sn as upsertPageAgentContractMarkdown, _n as hasStructuredPageAgentContract, ln as normalizeDocsRelated, un as renderDocsRelatedMarkdownLines, vn as normalizePageAgentFrontmatter } from "./agent-BFqyqEnC.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-D57JXQLj.mjs";
4
- import { a as normalizeAgentScopeValues, i as normalizeAgentLocale, n as agentVersionConstraintsOverlap, r as normalizeAgentFramework, t as agentVersionConstraintMatches } from "./agent-scope-CCaIY1aK.mjs";
5
- import matter from "gray-matter";
6
- import fs from "node:fs";
7
- import path from "node:path";
8
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
9
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
10
- import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
11
- import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
12
- import * as z from "zod/v4";
13
-
14
- //#region src/mcp.ts
15
- const DEFAULT_MCP_ROUTE = "/api/docs/mcp";
16
- const DEFAULT_MCP_VERSION = "0.0.0";
17
- const DEFAULT_MCP_NAME = "@farming-labs/docs";
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 = [
53
- {
54
- path: "entry",
55
- name: "entry",
56
- type: "string",
57
- default: "docs",
58
- description: "URL path prefix for documentation routes, for example \"docs\" creates /docs.",
59
- docs: "/docs/overview"
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
- },
69
- {
70
- path: "contentDir",
71
- name: "contentDir",
72
- type: "string",
73
- default: "same as entry",
74
- description: "Path to markdown content files. Adapters outside Next.js usually need this when content does not live under the route prefix.",
75
- docs: "/docs/overview"
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
- },
84
- {
85
- path: "staticExport",
86
- name: "staticExport",
87
- type: "boolean",
88
- default: false,
89
- description: "Enable full static builds. Search, AI, and runtime API routes are hidden.",
90
- docs: "/docs/overview"
91
- },
92
- {
93
- path: "theme",
94
- name: "theme",
95
- type: "DocsTheme",
96
- description: "Theme instance from a theme factory such as fumadocs() or pixelBorder().",
97
- docs: "/docs/customization/themes"
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
- },
118
- {
119
- path: "nav",
120
- name: "nav",
121
- type: "{ title?: string; url?: string }",
122
- description: "Sidebar and discovery metadata for the docs site. Non-Next.js adapters usually require it.",
123
- children: [{
124
- path: "nav.title",
125
- name: "title",
126
- type: "string",
127
- description: "Human-readable docs site title."
128
- }, {
129
- path: "nav.url",
130
- name: "url",
131
- type: "string",
132
- description: "Public base URL for generated absolute links and metadata."
133
- }]
134
- },
135
- {
136
- path: "github",
137
- name: "github",
138
- type: "string | GithubConfig",
139
- description: "GitHub repository metadata for \"Edit on GitHub\" links and page action prompt templates.",
140
- docs: "/docs/customization/page-actions"
141
- },
142
- {
143
- path: "themeToggle",
144
- name: "themeToggle",
145
- type: "boolean | ThemeToggleConfig",
146
- default: true,
147
- description: "Enable or customize the light/dark mode toggle."
148
- },
149
- {
150
- path: "breadcrumb",
151
- name: "breadcrumb",
152
- type: "boolean | BreadcrumbConfig",
153
- default: true,
154
- description: "Enable or customize breadcrumb navigation."
155
- },
156
- {
157
- path: "sidebar",
158
- name: "sidebar",
159
- type: "boolean | SidebarConfig",
160
- default: true,
161
- description: "Enable or customize the docs sidebar.",
162
- children: [{
163
- path: "sidebar.style",
164
- name: "style",
165
- type: "string",
166
- description: "Theme-specific sidebar style variant when supported."
167
- }, {
168
- path: "sidebar.defaultOpen",
169
- name: "defaultOpen",
170
- type: "boolean",
171
- description: "Whether collapsible sidebar groups start open by default."
172
- }]
173
- },
174
- {
175
- path: "icons",
176
- name: "icons",
177
- type: "Record<string, Component>",
178
- description: "Shared icon registry for frontmatter icon fields and built-in MDX components."
179
- },
180
- {
181
- path: "components",
182
- name: "components",
183
- type: "Record<string, Component>",
184
- description: "Custom MDX component registry and built-in component overrides."
185
- },
186
- {
187
- path: "onCopyClick",
188
- name: "onCopyClick",
189
- type: "(data: CodeBlockCopyData) => void",
190
- description: "Callback fired when a visitor copies a code block, including title, content, url, and language."
191
- },
192
- {
193
- path: "feedback",
194
- name: "feedback",
195
- type: "boolean | FeedbackConfig",
196
- default: false,
197
- description: "Human page feedback UI. Agent feedback endpoints remain default-on unless opted out.",
198
- docs: "/docs/customization/feedback"
199
- },
200
- {
201
- path: "readingTime",
202
- name: "readingTime",
203
- type: "boolean | ReadingTimeConfig",
204
- default: false,
205
- description: "Opt-in estimated reading time label with per-page overrides and label format."
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
- },
219
- {
220
- path: "agent",
221
- name: "agent",
222
- type: "DocsAgentConfig",
223
- description: "Agent compaction defaults and offline-by-default 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: "Offline-by-default golden tasks for retrieval, citation, version, example, answer, and budget evaluation, with explicit external-provider and execution opt-ins.",
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.surface",
310
- name: "surface",
311
- type: "\"mcp-context\" | \"configured-search\" | \"ask-ai-context\"",
312
- default: "mcp-context",
313
- description: "Retrieval/context surface measured by the golden task suite."
314
- },
315
- {
316
- path: "agent.evaluations.allowNetwork",
317
- name: "allowNetwork",
318
- type: "boolean",
319
- default: false,
320
- description: "Allow external search, HTTP answers, and explicit executable-example verification during evaluation."
321
- },
322
- {
323
- path: "agent.evaluations.searchTimeoutMs",
324
- name: "searchTimeoutMs",
325
- type: "number",
326
- default: 3e4,
327
- description: "Per-task configured search and Ask AI retrieval timeout in milliseconds."
328
- },
329
- {
330
- path: "agent.evaluations.answer",
331
- name: "answer",
332
- type: "DocsAgentEvaluationAnswerProvider",
333
- description: "Opt-in callback or HTTP provider used to evaluate actual generated answers and citations.",
334
- children: [
335
- {
336
- path: "agent.evaluations.answer.provider",
337
- name: "provider",
338
- type: "\"callback\" | \"http\"",
339
- description: "Answer evaluation provider kind."
340
- },
341
- {
342
- path: "agent.evaluations.answer.run",
343
- name: "run",
344
- type: "DocsAgentEvaluationAnswerRunner",
345
- description: "Callback invoked with retrieved context and source references."
346
- },
347
- {
348
- path: "agent.evaluations.answer.endpoint",
349
- name: "endpoint",
350
- type: "string",
351
- description: "HTTP endpoint used by the opt-in HTTP answer provider."
352
- },
353
- {
354
- path: "agent.evaluations.answer.headers",
355
- name: "headers",
356
- type: "Record<string, string>",
357
- description: "Optional HTTP request headers; values are never reported.",
358
- children: [{
359
- path: "agent.evaluations.answer.headers.*",
360
- name: "header",
361
- type: "string",
362
- description: "One custom HTTP header value."
363
- }]
364
- },
365
- {
366
- path: "agent.evaluations.answer.timeoutMs",
367
- name: "timeoutMs",
368
- type: "number",
369
- default: 3e4,
370
- description: "Callback or HTTP answer provider timeout in milliseconds."
371
- }
372
- ]
373
- },
374
- {
375
- path: "agent.evaluations.tasks",
376
- name: "tasks",
377
- type: "DocsAgentGoldenTask[]",
378
- description: "Golden task fixtures evaluated by docs doctor and docs review; the default MCP context surface runs offline.",
379
- children: [
380
- {
381
- path: "agent.evaluations.tasks[]",
382
- name: "task",
383
- type: "DocsAgentGoldenTask",
384
- description: "One golden task entry."
385
- },
386
- {
387
- path: "agent.evaluations.tasks[].id",
388
- name: "id",
389
- type: "string",
390
- description: "Stable task identifier shown in diagnostics."
391
- },
392
- {
393
- path: "agent.evaluations.tasks[].query",
394
- name: "query",
395
- type: "string",
396
- description: "User-shaped retrieval query."
397
- },
398
- {
399
- path: "agent.evaluations.tasks[].tokenBudget",
400
- name: "tokenBudget",
401
- type: "number",
402
- description: "Per-task UTF-8 context-byte ceiling override."
403
- },
404
- {
405
- path: "agent.evaluations.tasks[].topK",
406
- name: "topK",
407
- type: "number",
408
- description: "Per-task ranked retrieval depth override."
409
- },
410
- {
411
- path: "agent.evaluations.tasks[].surface",
412
- name: "surface",
413
- type: "\"mcp-context\" | \"configured-search\" | \"ask-ai-context\"",
414
- description: "Per-task evaluation surface override."
415
- },
416
- {
417
- path: "agent.evaluations.tasks[].filters",
418
- name: "filters",
419
- type: "DocsAgentGoldenTaskFilters",
420
- description: "Framework, version, and locale retrieval scope.",
421
- children: [
422
- {
423
- path: "agent.evaluations.tasks[].filters.framework",
424
- name: "framework",
425
- type: "string",
426
- description: "Required framework, such as nextjs or astro."
427
- },
428
- {
429
- path: "agent.evaluations.tasks[].filters.version",
430
- name: "version",
431
- type: "string",
432
- description: "Exact version requested by the task."
433
- },
434
- {
435
- path: "agent.evaluations.tasks[].filters.locale",
436
- name: "locale",
437
- type: "string",
438
- description: "Required locale."
439
- }
440
- ]
441
- },
442
- {
443
- path: "agent.evaluations.tasks[].expect",
444
- name: "expect",
445
- type: "DocsAgentGoldenTaskExpectation",
446
- description: "Evaluator-only source, rank, citation, scope, answer, example, and budget expectations.",
447
- children: [
448
- {
449
- path: "agent.evaluations.tasks[].expect.relevantSources",
450
- name: "relevantSources",
451
- type: "string[]",
452
- description: "Canonical page or section URLs that should answer the task."
453
- },
454
- {
455
- path: "agent.evaluations.tasks[].expect.allowedSources",
456
- name: "allowedSources",
457
- type: "string[]",
458
- description: "Additional legitimate citations that do not reduce precision."
459
- },
460
- {
461
- path: "agent.evaluations.tasks[].expect.forbiddenSources",
462
- name: "forbiddenSources",
463
- type: "string[]",
464
- description: "Sources that must not be retrieved or cited."
465
- },
466
- {
467
- path: "agent.evaluations.tasks[].expect.requiredCitations",
468
- name: "requiredCitations",
469
- type: "string[]",
470
- description: "Citations that must appear; defaults to relevantSources."
471
- },
472
- {
473
- path: "agent.evaluations.tasks[].expect.minRecallAtK",
474
- name: "minRecallAtK",
475
- type: "number",
476
- default: 1,
477
- description: "Minimum relevant-source recall in the top K results."
478
- },
479
- {
480
- path: "agent.evaluations.tasks[].expect.maxFirstRelevantRank",
481
- name: "maxFirstRelevantRank",
482
- type: "number",
483
- description: "Maximum acceptable rank of the first relevant source."
484
- },
485
- {
486
- path: "agent.evaluations.tasks[].expect.minUsefulByteRatio",
487
- name: "minUsefulByteRatio",
488
- type: "number",
489
- description: "Minimum share of context bytes supplied by relevant sources."
490
- },
491
- {
492
- path: "agent.evaluations.tasks[].expect.scope",
493
- name: "scope",
494
- type: "DocsAgentGoldenTaskFilters",
495
- description: "Framework, version, and locale assertions checked against returned sources without pre-filtering retrieval.",
496
- children: [
497
- {
498
- path: "agent.evaluations.tasks[].expect.scope.framework",
499
- name: "framework",
500
- type: "string",
501
- description: "Framework the returned sources must select."
502
- },
503
- {
504
- path: "agent.evaluations.tasks[].expect.scope.version",
505
- name: "version",
506
- type: "string",
507
- description: "Version the returned sources must select."
508
- },
509
- {
510
- path: "agent.evaluations.tasks[].expect.scope.locale",
511
- name: "locale",
512
- type: "string",
513
- description: "Locale the returned sources must select."
514
- }
515
- ]
516
- },
517
- {
518
- path: "agent.evaluations.tasks[].expect.answer",
519
- name: "answer",
520
- type: "DocsAgentGoldenAnswerExpectation",
521
- description: "Required answer text and citation assertions for an explicitly configured answer provider.",
522
- children: [
523
- {
524
- path: "agent.evaluations.tasks[].expect.answer.includes",
525
- name: "includes",
526
- type: "string[]",
527
- description: "Literal fragments required in the actual answer."
528
- },
529
- {
530
- path: "agent.evaluations.tasks[].expect.answer.excludes",
531
- name: "excludes",
532
- type: "string[]",
533
- description: "Literal fragments forbidden in the actual answer."
534
- },
535
- {
536
- path: "agent.evaluations.tasks[].expect.answer.requiredCitations",
537
- name: "requiredCitations",
538
- type: "string[]",
539
- description: "Citations required in the actual answer."
540
- },
541
- {
542
- path: "agent.evaluations.tasks[].expect.answer.allowedCitations",
543
- name: "allowedCitations",
544
- type: "string[]",
545
- description: "Additional valid actual-answer citations."
546
- },
547
- {
548
- path: "agent.evaluations.tasks[].expect.answer.forbiddenCitations",
549
- name: "forbiddenCitations",
550
- type: "string[]",
551
- description: "Citations forbidden in the actual answer."
552
- }
553
- ]
554
- },
555
- {
556
- path: "agent.evaluations.tasks[].expect.examples",
557
- name: "examples",
558
- type: "DocsAgentGoldenExpectedExample[]",
559
- description: "Runnable examples that must be present in returned context.",
560
- children: [
561
- {
562
- path: "agent.evaluations.tasks[].expect.examples[]",
563
- name: "example",
564
- type: "DocsAgentGoldenExpectedExample",
565
- description: "One expected code example."
566
- },
567
- {
568
- path: "agent.evaluations.tasks[].expect.examples[].source",
569
- name: "source",
570
- type: "string",
571
- description: "Canonical source URL containing the example."
572
- },
573
- {
574
- path: "agent.evaluations.tasks[].expect.examples[].language",
575
- name: "language",
576
- type: "string",
577
- description: "Expected code-fence language."
578
- },
579
- {
580
- path: "agent.evaluations.tasks[].expect.examples[].framework",
581
- name: "framework",
582
- type: "string",
583
- description: "Expected framework metadata."
584
- },
585
- {
586
- path: "agent.evaluations.tasks[].expect.examples[].packageManager",
587
- name: "packageManager",
588
- type: "string",
589
- description: "Expected package-manager metadata."
590
- },
591
- {
592
- path: "agent.evaluations.tasks[].expect.examples[].title",
593
- name: "title",
594
- type: "string",
595
- description: "Expected code-fence title metadata."
596
- },
597
- {
598
- path: "agent.evaluations.tasks[].expect.examples[].runnable",
599
- name: "runnable",
600
- type: "boolean",
601
- default: true,
602
- description: "Whether the example must be marked runnable."
603
- },
604
- {
605
- path: "agent.evaluations.tasks[].expect.examples[].includes",
606
- name: "includes",
607
- type: "string[]",
608
- description: "Literal code fragments that must appear."
609
- },
610
- {
611
- path: "agent.evaluations.tasks[].expect.examples[].verification",
612
- name: "verification",
613
- type: "\"present\" | \"syntax\" | \"execute\"",
614
- description: "Required verification strength; defaults to present when runnable is false and syntax otherwise. Runtime execution is always explicit."
615
- }
616
- ]
617
- }
618
- ]
619
- }
620
- ]
621
- }
622
- ]
623
- }]
624
- },
625
- {
626
- path: "review",
627
- name: "review",
628
- type: "boolean | DocsReviewConfig",
629
- description: "Docs review scoring, CI behavior, and diagnostic rule severities.",
630
- docs: "/docs/reference",
631
- children: [
632
- {
633
- path: "review.enabled",
634
- name: "enabled",
635
- type: "boolean",
636
- default: true,
637
- description: "Enable Docs Review."
638
- },
639
- {
640
- path: "review.score",
641
- name: "score",
642
- type: "DocsReviewScoreConfig",
643
- description: "Healthy threshold and finding severity weights.",
644
- children: [{
645
- path: "review.score.threshold",
646
- name: "threshold",
647
- type: "number",
648
- default: 80,
649
- description: "Minimum healthy review score."
650
- }, {
651
- path: "review.score.weights",
652
- name: "weights",
653
- type: "{ error?: number; warn?: number; suggestion?: number }",
654
- description: "Point deductions for each finding severity.",
655
- children: [
656
- {
657
- path: "review.score.weights.error",
658
- name: "error",
659
- type: "number",
660
- default: 20,
661
- description: "Point deduction for an error finding."
662
- },
663
- {
664
- path: "review.score.weights.warn",
665
- name: "warn",
666
- type: "number",
667
- default: 8,
668
- description: "Point deduction for a warning finding."
669
- },
670
- {
671
- path: "review.score.weights.suggestion",
672
- name: "suggestion",
673
- type: "number",
674
- default: 2,
675
- description: "Point deduction for a suggestion finding."
676
- }
677
- ]
678
- }]
679
- },
680
- {
681
- path: "review.ci",
682
- name: "ci",
683
- type: "boolean | DocsReviewCiConfig",
684
- description: "GitHub Actions reporting and blocking behavior.",
685
- children: [
686
- {
687
- path: "review.ci.enabled",
688
- name: "enabled",
689
- type: "boolean",
690
- default: true,
691
- description: "Enable review workflow generation."
692
- },
693
- {
694
- path: "review.ci.name",
695
- name: "name",
696
- type: "string",
697
- default: "docs-review",
698
- description: "GitHub Actions job and check name."
699
- },
700
- {
701
- path: "review.ci.mode",
702
- name: "mode",
703
- type: "\"off\" | \"warn\" | \"block\"",
704
- default: "warn",
705
- description: "Whether CI is disabled, advisory, or blocking.",
706
- values: [
707
- "off",
708
- "warn",
709
- "block"
710
- ]
711
- },
712
- {
713
- path: "review.ci.annotations",
714
- name: "annotations",
715
- type: "boolean",
716
- default: true,
717
- description: "Emit GitHub workflow annotations."
718
- },
719
- {
720
- path: "review.ci.comment",
721
- name: "comment",
722
- type: "boolean",
723
- default: true,
724
- description: "Allow the official action or bot to post PR comments."
725
- }
726
- ]
727
- },
728
- {
729
- path: "review.rules",
730
- name: "rules",
731
- type: "DocsReviewRulesConfig",
732
- description: "Per-rule severity overrides.",
733
- children: [
734
- ["brokenLinks", "error"],
735
- ["frontmatter", "error"],
736
- ["duplicateSlugs", "error"],
737
- ["invalidMdx", "error"],
738
- ["configExamples", "warn"],
739
- ["codeFenceMetadata", "warn"],
740
- ["runnableMetadata", "warn"],
741
- ["agentContext", "warn"],
742
- ["commandHealth", "warn"],
743
- ["relatedCoverage", "suggestion"],
744
- ["configConfidence", "warn"],
745
- ["agentSurfaceDrift", "error"],
746
- ["goldenTasks", "warn"]
747
- ].map(([name, defaultValue]) => ({
748
- path: `review.rules.${name}`,
749
- name,
750
- type: "DocsReviewSeverity",
751
- default: defaultValue,
752
- description: `Severity override for the ${name} review rule.`,
753
- values: [
754
- "off",
755
- "suggestion",
756
- "warn",
757
- "error"
758
- ]
759
- }))
760
- }
761
- ]
762
- },
763
- {
764
- path: "pageActions",
765
- name: "pageActions",
766
- type: "PageActionsConfig",
767
- description: "Copy Markdown and Open in LLM actions for docs pages.",
768
- docs: "/docs/customization/page-actions",
769
- children: [{
770
- path: "pageActions.copyMarkdown",
771
- name: "copyMarkdown",
772
- type: "boolean | PageActionConfig",
773
- description: "Show a Copy Markdown action for the current page."
774
- }, {
775
- path: "pageActions.openDocs",
776
- name: "openDocs",
777
- type: "boolean | OpenDocsActionConfig",
778
- description: "Show provider actions that open the current docs page in an LLM.",
779
- children: [
780
- {
781
- path: "pageActions.openDocs.target",
782
- name: "target",
783
- type: "\"page\" | \"markdown\"",
784
- default: "page",
785
- description: "Whether provider URLs receive the rendered page URL or the .md markdown route."
786
- },
787
- {
788
- path: "pageActions.openDocs.providers",
789
- name: "providers",
790
- type: "Array<string | PromptProviderConfig>",
791
- description: "Provider IDs or provider objects. Built-ins include chatgpt, claude, cursor, and t3."
792
- },
793
- {
794
- path: "pageActions.openDocs.prompt",
795
- name: "prompt",
796
- type: "string",
797
- description: "Prompt text prepended to the provider URL when opening docs."
798
- }
799
- ]
800
- }]
801
- },
802
- {
803
- path: "ai",
804
- name: "ai",
805
- type: "AIConfig",
806
- description: "RAG-powered Ask AI configuration.",
807
- docs: "/docs/customization/ask-ai",
808
- children: [
809
- {
810
- path: "ai.enabled",
811
- name: "enabled",
812
- type: "boolean",
813
- description: "Enable or disable Ask AI."
814
- },
815
- {
816
- path: "ai.model",
817
- name: "model",
818
- type: "string | AIModelConfig",
819
- description: "Model ID or model routing config."
820
- },
821
- {
822
- path: "ai.providers",
823
- name: "providers",
824
- type: "Record<string, AIProviderConfig>",
825
- description: "Provider base URLs and optional API keys."
826
- },
827
- {
828
- path: "ai.systemPrompt",
829
- name: "systemPrompt",
830
- type: "string",
831
- description: "Additional instruction text for generated answers."
832
- },
833
- {
834
- path: "ai.useMcp",
835
- name: "useMcp",
836
- type: "boolean | DocsAskAIMcpConfig",
837
- description: "Use the built-in MCP search tool as Ask AI's retrieval provider."
838
- }
839
- ]
840
- },
841
- {
842
- path: "search",
843
- name: "search",
844
- type: "boolean | DocsSearchConfig",
845
- default: true,
846
- description: "Built-in simple search, Typesense, Algolia, MCP, or a custom adapter.",
847
- docs: "/docs/customization/search",
848
- children: [{
849
- path: "search.provider",
850
- name: "provider",
851
- type: "\"simple\" | \"typesense\" | \"algolia\" | \"mcp\" | \"custom\"",
852
- default: "simple",
853
- description: "Search backend used by the docs UI and MCP search tool."
854
- }, {
855
- path: "search.maxResults",
856
- name: "maxResults",
857
- type: "number",
858
- description: "Maximum result count returned by search requests."
859
- }]
860
- },
861
- {
862
- path: "cloud",
863
- name: "cloud",
864
- type: "DocsCloudConfig",
865
- description: "Docs Cloud integration settings mirrored into docs.json by cloud CLI commands.",
866
- children: [
867
- {
868
- path: "cloud.apiKey.env",
869
- name: "env",
870
- type: "string",
871
- default: "DOCS_CLOUD_API_KEY",
872
- description: "Environment variable that stores the Docs Cloud API key. The key value is never written to docs.json."
873
- },
874
- {
875
- path: "cloud.deploy.enabled",
876
- name: "enabled",
877
- type: "boolean",
878
- default: true,
879
- description: "Enable the docs deploy command for hosted preview docs."
880
- },
881
- {
882
- path: "cloud.publish.mode",
883
- name: "mode",
884
- type: "\"draft-pr\" | \"direct-commit\"",
885
- default: "draft-pr",
886
- description: "How Docs Cloud publishes generated docs changes."
887
- },
888
- {
889
- path: "cloud.publish.baseBranch",
890
- name: "baseBranch",
891
- type: "string",
892
- default: "main",
893
- description: "Branch generated docs work should target."
894
- }
895
- ]
896
- },
897
- {
898
- path: "llmsTxt",
899
- name: "llmsTxt",
900
- type: "boolean | LlmsTxtConfig",
901
- default: true,
902
- description: "Generated /llms.txt, /llms-full.txt, optional section files, and basePath-aware aliases.",
903
- docs: "/docs/getting-started/agent-ready-docs"
904
- },
905
- {
906
- path: "changelog",
907
- name: "changelog",
908
- type: "boolean | ChangelogConfig",
909
- default: false,
910
- description: "Generate changelog feed and entry pages from dated MDX entries.",
911
- docs: "/docs/customization/changelog"
912
- },
913
- {
914
- path: "mcp",
915
- name: "mcp",
916
- type: "boolean | DocsMcpConfig",
917
- default: true,
918
- description: "Built-in MCP server over stdio plus HTTP routes at /mcp and /.well-known/mcp, backed by /api/docs/mcp.",
919
- docs: "/docs/customization/mcp",
920
- children: [
921
- {
922
- path: "mcp.enabled",
923
- name: "enabled",
924
- type: "boolean",
925
- default: true,
926
- description: "Enable the built-in MCP server."
927
- },
928
- {
929
- path: "mcp.route",
930
- name: "route",
931
- type: "string",
932
- default: "/api/docs/mcp",
933
- description: "Canonical Streamable HTTP route used by the MCP endpoint."
934
- },
935
- {
936
- path: "mcp.name",
937
- name: "name",
938
- type: "string",
939
- default: "nav.title or @farming-labs/docs",
940
- description: "Human-readable MCP server name reported to clients."
941
- },
942
- {
943
- path: "mcp.version",
944
- name: "version",
945
- type: "string",
946
- default: "0.0.0",
947
- description: "Version string reported to MCP clients."
948
- },
949
- {
950
- path: "mcp.security",
951
- name: "security",
952
- type: "DocsMcpSecurityConfig",
953
- description: "Streamable HTTP Origin validation, optional authentication, and request-size controls. The stdio transport is unaffected.",
954
- children: [
955
- {
956
- path: "mcp.security.allowedOrigins",
957
- name: "allowedOrigins",
958
- type: "\"same-origin\" | string[] | callback",
959
- default: "same-origin",
960
- 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."
961
- },
962
- {
963
- path: "mcp.security.authenticate",
964
- name: "authenticate",
965
- type: "DocsMcpAuthenticate",
966
- default: "public (callback omitted)",
967
- description: "Opt-in HTTP authentication callback. Return a principal to continue, null for 401, or a Response to control the rejection."
968
- },
969
- {
970
- path: "mcp.security.maxBodyBytes",
971
- name: "maxBodyBytes",
972
- type: "number",
973
- default: DEFAULT_DOCS_MCP_MAX_BODY_BYTES,
974
- description: "Maximum accepted Streamable HTTP POST body size in bytes."
975
- },
976
- {
977
- path: "mcp.security.cors",
978
- name: "cors",
979
- type: "boolean | DocsMcpCorsConfig",
980
- default: true,
981
- description: "Emit exact-Origin CORS responses for Origins accepted by allowedOrigins. Use an object for credentials, additional headers, and preflight cache controls.",
982
- children: [
983
- {
984
- path: "mcp.security.cors.allowedHeaders",
985
- name: "allowedHeaders",
986
- type: "string[]",
987
- description: "Additional request headers accepted during browser preflight."
988
- },
989
- {
990
- path: "mcp.security.cors.exposedHeaders",
991
- name: "exposedHeaders",
992
- type: "string[]",
993
- description: "Additional MCP response headers exposed to browser JavaScript."
994
- },
995
- {
996
- path: "mcp.security.cors.allowCredentials",
997
- name: "allowCredentials",
998
- type: "boolean",
999
- default: false,
1000
- description: "Allow credentialed browser requests using the validated exact Origin. Wildcard credentials are never emitted."
1001
- },
1002
- {
1003
- path: "mcp.security.cors.maxAgeSeconds",
1004
- name: "maxAgeSeconds",
1005
- type: "number",
1006
- default: DEFAULT_DOCS_MCP_CORS_MAX_AGE_SECONDS,
1007
- description: "Browser preflight cache lifetime in seconds."
1008
- }
1009
- ]
1010
- }
1011
- ]
1012
- },
1013
- {
1014
- path: "mcp.tools",
1015
- name: "tools",
1016
- type: "DocsMcpToolsConfig",
1017
- default: "all enabled",
1018
- description: "Fine-grained built-in MCP tool toggles.",
1019
- children: [
1020
- {
1021
- path: "mcp.tools.listDocs",
1022
- name: "listDocs",
1023
- type: "boolean",
1024
- default: true,
1025
- description: "Expose the list_docs tool."
1026
- },
1027
- {
1028
- path: "mcp.tools.listPages",
1029
- name: "listPages",
1030
- type: "boolean",
1031
- default: true,
1032
- description: "Expose the list_pages tool."
1033
- },
1034
- {
1035
- path: "mcp.tools.listTasks",
1036
- name: "listTasks",
1037
- type: "boolean",
1038
- default: true,
1039
- description: "Expose the list_tasks tool."
1040
- },
1041
- {
1042
- path: "mcp.tools.readTask",
1043
- name: "readTask",
1044
- type: "boolean",
1045
- default: true,
1046
- description: "Expose the read_task tool."
1047
- },
1048
- {
1049
- path: "mcp.tools.getNavigation",
1050
- name: "getNavigation",
1051
- type: "boolean",
1052
- default: true,
1053
- description: "Expose the get_navigation tool."
1054
- },
1055
- {
1056
- path: "mcp.tools.searchDocs",
1057
- name: "searchDocs",
1058
- type: "boolean",
1059
- default: true,
1060
- description: "Expose the search_docs tool."
1061
- },
1062
- {
1063
- path: "mcp.tools.readPage",
1064
- name: "readPage",
1065
- type: "boolean",
1066
- default: true,
1067
- description: "Expose the read_page tool."
1068
- },
1069
- {
1070
- path: "mcp.tools.getCodeExamples",
1071
- name: "getCodeExamples",
1072
- type: "boolean",
1073
- default: true,
1074
- description: "Expose the get_code_examples tool."
1075
- },
1076
- {
1077
- path: "mcp.tools.getConfigSchema",
1078
- name: "getConfigSchema",
1079
- type: "boolean",
1080
- default: true,
1081
- description: "Expose the get_config_schema tool."
1082
- },
1083
- {
1084
- path: "mcp.tools.getContext",
1085
- name: "getContext",
1086
- type: "boolean",
1087
- default: true,
1088
- description: "Expose deterministic get_context retrieval with a conservative UTF-8 byte ceiling."
1089
- }
1090
- ]
1091
- }
1092
- ]
1093
- },
1094
- {
1095
- path: "apiReference",
1096
- name: "apiReference",
1097
- type: "boolean | ApiReferenceConfig",
1098
- default: false,
1099
- description: "Generated API reference pages from framework route conventions or a hosted OpenAPI document.",
1100
- docs: "/docs/customization/api-reference",
1101
- children: [{
1102
- path: "apiReference.specUrl",
1103
- name: "specUrl",
1104
- type: "string",
1105
- description: "Remote OpenAPI JSON URL when the backend owns the schema."
1106
- }, {
1107
- path: "apiReference.path",
1108
- name: "path",
1109
- type: "string",
1110
- description: "Docs route where the API reference is rendered."
1111
- }]
1112
- },
1113
- {
1114
- path: "codeBlocks",
1115
- name: "codeBlocks",
1116
- type: "{ validate?: boolean | DocsCodeBlocksValidateConfig }",
1117
- default: false,
1118
- description: "Code block intelligence for MD/MDX fences, including execution planning and optional sandboxed validation.",
1119
- docs: "/docs/configuration#code-block-validation",
1120
- children: [
1121
- {
1122
- path: "codeBlocks.validate",
1123
- name: "validate",
1124
- type: "boolean | DocsCodeBlocksValidateConfig",
1125
- description: "Enable `docs codeblocks validate` for fenced code examples."
1126
- },
1127
- {
1128
- path: "codeBlocks.validate.planner",
1129
- name: "planner",
1130
- type: "\"metadata\" | \"openai\" | \"openai-compatible\" | \"cloud\" | DocsCodeBlocksPlannerConfig",
1131
- default: "metadata",
1132
- description: "Planner that turns code fence metadata into an execution plan. Use OpenAI-compatible providers when metadata alone is not enough."
1133
- },
1134
- {
1135
- path: "codeBlocks.validate.runner",
1136
- name: "runner",
1137
- type: "\"local\" | \"vercel-sandbox\" | \"e2b\" | \"daytona\" | \"cloud\" | DocsCodeBlocksRunnerConfig",
1138
- default: "local",
1139
- description: "Runner used to execute planned snippets. Vercel Sandbox, E2B, and Daytona use provider tokens from env vars."
1140
- },
1141
- {
1142
- path: "codeBlocks.validate.env",
1143
- name: "env",
1144
- type: "Record<string, string>",
1145
- description: "Runtime env mapping, for example `{ OPENAI_API_KEY: \"OPENAI_TEST_API_KEY\" }`."
1146
- }
1147
- ]
1148
- },
1149
- {
1150
- path: "sitemap",
1151
- name: "sitemap",
1152
- type: "boolean | DocsSitemapConfig",
1153
- default: true,
1154
- description: "Generated sitemap.xml, sitemap.md, /docs/sitemap.md, and /.well-known/sitemap.md."
1155
- },
1156
- {
1157
- path: "robots",
1158
- name: "robots",
1159
- type: "boolean | DocsRobotsConfig",
1160
- default: true,
1161
- description: "Runtime or generated robots.txt policy for docs routes, agent-readable files, and AI crawler user agents."
1162
- },
1163
- {
1164
- path: "metadata",
1165
- name: "metadata",
1166
- type: "DocsMetadata",
1167
- description: "SEO and JSON-LD inputs such as titleTemplate and description."
1168
- },
1169
- {
1170
- path: "og",
1171
- name: "og",
1172
- type: "OGConfig",
1173
- description: "Dynamic Open Graph image configuration."
1174
- }
1175
- ];
1176
- const DOCS_CONFIG_SCHEMA_OPTIONS = freezeDocsConfigSchemaOptions(DOCS_CONFIG_SCHEMA_OPTIONS_TEMPLATE);
1177
- const DOCS_CONFIG_SCHEMA_EXAMPLES = freezeDocsConfigSchemaExamples([
1178
- {
1179
- title: "Minimal config",
1180
- code: `import { defineDocs } from "@farming-labs/docs";
1181
- import { fumadocs } from "@farming-labs/theme";
1182
-
1183
- export default defineDocs({
1184
- entry: "docs",
1185
- theme: fumadocs(),
1186
- });`
1187
- },
1188
- {
1189
- title: "MCP tool toggles",
1190
- code: `export default defineDocs({
1191
- entry: "docs",
1192
- mcp: {
1193
- tools: {
1194
- listDocs: true,
1195
- getConfigSchema: true,
1196
- getCodeExamples: true,
1197
- },
1198
- },
1199
- });`
1200
- },
1201
- {
1202
- title: "Opt-in MCP authentication",
1203
- code: `export default defineDocs({
1204
- mcp: {
1205
- security: {
1206
- async authenticate({ request }) {
1207
- const user = await authenticateRequest(request);
1208
- return user ? { id: user.id, scopes: ["docs:read"] } : null;
1209
- },
1210
- },
1211
- },
1212
- });`
1213
- },
1214
- {
1215
- title: "Code block validation",
1216
- code: `export default defineDocs({
1217
- entry: "docs",
1218
- codeBlocks: {
1219
- validate: {
1220
- planner: {
1221
- provider: "openai",
1222
- model: "gpt-4.1-mini",
1223
- apiKeyEnv: "OPENAI_API_KEY",
1224
- },
1225
- runner: {
1226
- provider: "vercel-sandbox",
1227
- tokenEnv: "VERCEL_TOKEN",
1228
- },
1229
- env: {
1230
- OPENAI_API_KEY: "OPENAI_TEST_API_KEY",
1231
- },
1232
- },
1233
- },
1234
- });`
1235
- }
1236
- ]);
1237
- const searchDocsInputSchema = z.object({
1238
- query: z.string().trim().min(1),
1239
- limit: z.number().int().min(1).max(25).optional(),
1240
- locale: z.string().min(1).optional()
1241
- });
1242
- const readPageInputSchema = z.object({
1243
- path: z.string().min(1),
1244
- locale: z.string().min(1).optional(),
1245
- section: z.string().trim().min(1).optional(),
1246
- maxChars: z.number().int().min(256).max(1e6).optional()
1247
- });
1248
- const listTasksInputSchema = z.object({
1249
- query: z.string().trim().min(1).optional(),
1250
- framework: z.string().trim().min(1).optional(),
1251
- version: z.string().trim().min(1).optional(),
1252
- package: z.string().trim().min(1).optional(),
1253
- locale: z.string().min(1).optional()
1254
- });
1255
- const readTaskInputSchema = readPageInputSchema;
1256
- const pageAgentAppliesToOutputSchema = z.object({
1257
- framework: z.array(z.string()).optional(),
1258
- version: z.array(z.string()).optional(),
1259
- package: z.array(z.string()).optional()
1260
- });
1261
- const pageAgentCommandOutputSchema = z.union([z.string(), z.object({
1262
- run: z.string(),
1263
- cwd: z.string().optional(),
1264
- description: z.string().optional()
1265
- })]);
1266
- const pageAgentVerificationOutputSchema = z.union([z.string(), z.object({
1267
- description: z.string().optional(),
1268
- run: z.string().optional(),
1269
- expect: z.string().optional()
1270
- })]);
1271
- const pageAgentFailureModeOutputSchema = z.union([z.string(), z.object({
1272
- symptom: z.string(),
1273
- resolution: z.string().optional()
1274
- })]);
1275
- const pageAgentContractOutputSchema = z.object({
1276
- tokenBudget: z.number().optional(),
1277
- task: z.string().optional(),
1278
- outcome: z.string().optional(),
1279
- appliesTo: pageAgentAppliesToOutputSchema.optional(),
1280
- prerequisites: z.array(z.string()).optional(),
1281
- files: z.array(z.string()).optional(),
1282
- commands: z.array(pageAgentCommandOutputSchema).optional(),
1283
- sideEffects: z.array(z.string()).optional(),
1284
- verification: z.array(pageAgentVerificationOutputSchema).optional(),
1285
- rollback: z.array(z.string()).optional(),
1286
- failureModes: z.array(pageAgentFailureModeOutputSchema).optional()
1287
- });
1288
- const taskSummaryOutputSchema = z.object({
1289
- slug: z.string(),
1290
- url: z.string(),
1291
- title: z.string(),
1292
- description: z.string().optional(),
1293
- task: z.string().optional(),
1294
- outcome: z.string().optional(),
1295
- appliesTo: pageAgentAppliesToOutputSchema.optional()
1296
- });
1297
- const listTasksOutputSchema = z.object({
1298
- resultCount: z.number().int().nonnegative(),
1299
- tasks: z.array(taskSummaryOutputSchema)
1300
- });
1301
- const readTaskOutputSchema = z.object({
1302
- page: z.object({
1303
- slug: z.string(),
1304
- url: z.string(),
1305
- title: z.string(),
1306
- description: z.string().optional(),
1307
- sourcePath: z.string().optional(),
1308
- lastModified: z.string().optional()
1309
- }),
1310
- contract: pageAgentContractOutputSchema
1311
- });
1312
- const listPagesInputSchema = z.object({ locale: z.string().min(1).optional() });
1313
- const listDocsInputSchema = z.object({
1314
- section: z.string().trim().min(1).optional(),
1315
- locale: z.string().min(1).optional()
1316
- });
1317
- const getNavigationInputSchema = z.object({ locale: z.string().min(1).optional() });
1318
- const getConfigSchemaInputSchema = z.object({
1319
- option: z.string().trim().min(1).optional(),
1320
- query: z.string().trim().min(1).optional()
1321
- });
1322
- const getCodeExamplesInputSchema = z.object({
1323
- query: z.string().trim().min(1).optional(),
1324
- path: z.string().min(1).optional(),
1325
- framework: z.string().trim().min(1).optional(),
1326
- packageManager: z.string().trim().min(1).optional(),
1327
- language: z.string().trim().min(1).optional(),
1328
- runnable: z.boolean().optional(),
1329
- limit: z.number().int().min(1).max(50).optional(),
1330
- locale: z.string().min(1).optional()
1331
- });
1332
- const getContextInputSchema = z.object({
1333
- query: z.string().trim().min(1),
1334
- framework: z.string().trim().min(1).optional(),
1335
- version: z.string().trim().min(1).optional(),
1336
- locale: z.string().trim().min(1).optional(),
1337
- tokenBudget: z.number().int().min(MIN_MCP_CONTEXT_TOKEN_BUDGET).max(MAX_MCP_CONTEXT_TOKEN_BUDGET).default(DEFAULT_MCP_CONTEXT_TOKEN_BUDGET)
1338
- });
1339
- const relatedLinkOutputSchema = z.object({ href: z.string() });
1340
- const pageAgentContractSummaryOutputSchema = z.object({
1341
- hasContract: z.boolean(),
1342
- task: z.string().optional(),
1343
- outcome: z.string().optional(),
1344
- appliesTo: pageAgentAppliesToOutputSchema.optional()
1345
- });
1346
- const pageSummaryOutputSchema = z.object({
1347
- slug: z.string(),
1348
- url: z.string(),
1349
- title: z.string(),
1350
- description: z.string().optional(),
1351
- agent: pageAgentContractSummaryOutputSchema.optional(),
1352
- icon: z.string().optional(),
1353
- sourcePath: z.string().optional(),
1354
- lastModified: z.string().optional()
1355
- });
1356
- const docsSectionOutputSchema = z.lazy(() => z.object({
1357
- slug: z.string(),
1358
- title: z.string(),
1359
- url: z.string().optional(),
1360
- description: z.string().optional(),
1361
- icon: z.string().optional(),
1362
- pageCount: z.number().int().nonnegative(),
1363
- pages: z.array(pageSummaryOutputSchema),
1364
- sections: z.array(docsSectionOutputSchema)
1365
- }));
1366
- const listPagesOutputSchema = z.object({ pages: z.array(pageSummaryOutputSchema) });
1367
- const listDocsOutputSchema = z.object({
1368
- section: z.string().optional(),
1369
- resultCount: z.number().int().nonnegative(),
1370
- sectionCount: z.number().int().nonnegative(),
1371
- pages: z.array(pageSummaryOutputSchema),
1372
- rootPages: z.array(pageSummaryOutputSchema),
1373
- sections: z.array(docsSectionOutputSchema)
1374
- });
1375
- const navigationPageOutputSchema = z.object({
1376
- type: z.literal("page"),
1377
- name: z.string(),
1378
- url: z.string(),
1379
- icon: z.string().optional(),
1380
- description: z.string().optional()
1381
- });
1382
- const navigationNodeOutputSchema = z.lazy(() => z.discriminatedUnion("type", [navigationPageOutputSchema, z.object({
1383
- type: z.literal("folder"),
1384
- name: z.string(),
1385
- icon: z.string().optional(),
1386
- index: navigationPageOutputSchema.optional(),
1387
- children: z.array(navigationNodeOutputSchema)
1388
- })]));
1389
- const navigationOutputSchema = z.object({
1390
- navigation: z.object({
1391
- name: z.string(),
1392
- children: z.array(navigationNodeOutputSchema)
1393
- }),
1394
- markdown: z.string()
1395
- });
1396
- const searchResultOutputSchema = z.object({
1397
- id: z.string(),
1398
- url: z.string(),
1399
- content: z.string(),
1400
- description: z.string().optional(),
1401
- type: z.enum([
1402
- "page",
1403
- "heading",
1404
- "text"
1405
- ]),
1406
- score: z.number().optional(),
1407
- section: z.string().optional()
1408
- });
1409
- const searchDocsOutputSchema = z.object({ results: z.array(searchResultOutputSchema) });
1410
- const codeExampleOutputSchema = z.object({
1411
- id: z.string(),
1412
- page: z.object({
1413
- slug: z.string(),
1414
- url: z.string(),
1415
- title: z.string(),
1416
- description: z.string().optional(),
1417
- sourcePath: z.string().optional(),
1418
- lastModified: z.string().optional()
1419
- }),
1420
- language: z.string().optional(),
1421
- title: z.string().optional(),
1422
- framework: z.string().optional(),
1423
- packageManager: z.string().optional(),
1424
- runnable: z.boolean(),
1425
- meta: z.record(z.string(), z.union([z.string(), z.boolean()])),
1426
- code: z.string()
1427
- });
1428
- const codeExamplesOutputSchema = z.object({ examples: z.array(codeExampleOutputSchema) });
1429
- const configSchemaOptionOutputSchema = z.lazy(() => z.object({
1430
- path: z.string(),
1431
- name: z.string(),
1432
- type: z.string(),
1433
- default: z.union([
1434
- z.string(),
1435
- z.boolean(),
1436
- z.number(),
1437
- z.null()
1438
- ]).optional(),
1439
- description: z.string(),
1440
- docs: z.string().optional(),
1441
- values: z.array(z.string()).optional(),
1442
- children: z.array(configSchemaOptionOutputSchema).optional()
1443
- }));
1444
- const configSchemaOutputSchema = z.object({
1445
- schemaVersion: z.literal(1),
1446
- configFile: z.literal("docs.config.ts"),
1447
- description: z.string(),
1448
- filters: z.object({
1449
- option: z.string().optional(),
1450
- query: z.string().optional()
1451
- }).optional(),
1452
- resultCount: z.number().int().nonnegative(),
1453
- options: z.array(configSchemaOptionOutputSchema),
1454
- examples: z.array(z.object({
1455
- title: z.string(),
1456
- code: z.string()
1457
- }))
1458
- });
1459
- const readPageOutputSchema = z.object({
1460
- page: z.object({
1461
- slug: z.string(),
1462
- url: z.string(),
1463
- title: z.string(),
1464
- description: z.string().optional(),
1465
- related: z.array(relatedLinkOutputSchema).optional(),
1466
- icon: z.string().optional(),
1467
- sourcePath: z.string().optional(),
1468
- lastModified: z.string().optional(),
1469
- locale: z.string().optional(),
1470
- framework: z.string().optional(),
1471
- version: z.string().optional(),
1472
- tags: z.array(z.string()).optional()
1473
- }),
1474
- document: z.string(),
1475
- section: z.string().optional(),
1476
- anchor: z.string().optional(),
1477
- chars: z.number().int().nonnegative(),
1478
- totalChars: z.number().int().nonnegative(),
1479
- truncated: z.boolean()
1480
- });
1481
- const contextSourceOutputSchema = z.object({
1482
- id: z.string(),
1483
- title: z.string(),
1484
- pageUrl: z.string(),
1485
- url: z.string(),
1486
- section: z.string().optional(),
1487
- anchor: z.string().optional(),
1488
- sourcePath: z.string().optional(),
1489
- lastModified: z.string().optional(),
1490
- locale: z.string().optional(),
1491
- framework: z.string().optional(),
1492
- version: z.string().optional(),
1493
- tags: z.array(z.string()).optional(),
1494
- score: z.number().optional(),
1495
- content: z.string(),
1496
- chars: z.number().int().nonnegative(),
1497
- utf8Bytes: z.number().int().nonnegative(),
1498
- truncated: z.boolean()
1499
- });
1500
- const contextOutputSchema = z.object({
1501
- query: z.string(),
1502
- filters: z.object({
1503
- framework: z.string().optional(),
1504
- version: z.string().optional(),
1505
- locale: z.string().optional()
1506
- }),
1507
- budget: z.object({
1508
- requestedTokens: z.number().int().positive(),
1509
- strategy: z.literal("utf8-bytes"),
1510
- maxUtf8Bytes: z.number().int().positive(),
1511
- usedUtf8Bytes: z.number().int().nonnegative(),
1512
- conservativeTokenUpperBound: z.number().int().nonnegative(),
1513
- remainingUtf8Bytes: z.number().int().nonnegative(),
1514
- truncated: z.boolean()
1515
- }),
1516
- resultCount: z.number().int().nonnegative(),
1517
- candidateCount: z.number().int().nonnegative(),
1518
- context: z.string(),
1519
- sources: z.array(contextSourceOutputSchema)
1520
- });
1521
- function normalizeDocsMcpRoute(route) {
1522
- if (!route || route.trim().length === 0) return DEFAULT_MCP_ROUTE;
1523
- const normalized = `/${route}`.replace(/\/+/g, "/");
1524
- return normalized !== "/" ? normalized.replace(/\/+$/, "") : DEFAULT_MCP_ROUTE;
1525
- }
1526
- function resolveDocsMcpConfig(mcp, defaults = {}) {
1527
- if (mcp === false) return {
1528
- enabled: false,
1529
- route: normalizeDocsMcpRoute(defaults.defaultRoute),
1530
- name: defaults.defaultName ?? DEFAULT_MCP_NAME,
1531
- version: defaults.defaultVersion ?? DEFAULT_MCP_VERSION,
1532
- tools: {
1533
- listDocs: true,
1534
- listPages: true,
1535
- readPage: true,
1536
- listTasks: true,
1537
- readTask: true,
1538
- searchDocs: true,
1539
- getNavigation: true,
1540
- getCodeExamples: true,
1541
- getConfigSchema: true,
1542
- getContext: true
1543
- },
1544
- security: resolveDocsMcpSecurityConfig()
1545
- };
1546
- const config = mcp && typeof mcp === "object" ? mcp : {};
1547
- return {
1548
- enabled: typeof mcp === "boolean" ? mcp : config.enabled ?? true,
1549
- route: normalizeDocsMcpRoute(config.route ?? defaults.defaultRoute),
1550
- name: config.name ?? defaults.defaultName ?? DEFAULT_MCP_NAME,
1551
- version: config.version ?? defaults.defaultVersion ?? DEFAULT_MCP_VERSION,
1552
- tools: {
1553
- listDocs: config.tools?.listDocs ?? true,
1554
- listPages: config.tools?.listPages ?? true,
1555
- readPage: config.tools?.readPage ?? true,
1556
- listTasks: config.tools?.listTasks ?? true,
1557
- readTask: config.tools?.readTask ?? true,
1558
- searchDocs: config.tools?.searchDocs ?? true,
1559
- getNavigation: config.tools?.getNavigation ?? true,
1560
- getCodeExamples: config.tools?.getCodeExamples ?? true,
1561
- getConfigSchema: config.tools?.getConfigSchema ?? true,
1562
- getContext: config.tools?.getContext ?? true
1563
- },
1564
- security: resolveDocsMcpSecurityConfig(config.security)
1565
- };
1566
- }
1567
- function resolveDocsMcpSecurityConfig(security) {
1568
- const configuredMaxBodyBytes = security?.maxBodyBytes;
1569
- const maxBodyBytes = typeof configuredMaxBodyBytes === "number" && Number.isFinite(configuredMaxBodyBytes) && configuredMaxBodyBytes > 0 ? Math.floor(configuredMaxBodyBytes) : DEFAULT_DOCS_MCP_MAX_BODY_BYTES;
1570
- return {
1571
- allowedOrigins: security?.allowedOrigins ?? "same-origin",
1572
- authenticate: security?.authenticate,
1573
- maxBodyBytes,
1574
- cors: resolveDocsMcpCorsConfig(security?.cors)
1575
- };
1576
- }
1577
- function resolveDocsMcpCorsConfig(cors) {
1578
- const config = cors && typeof cors === "object" ? cors : {};
1579
- const configuredMaxAge = config.maxAgeSeconds;
1580
- const maxAgeSeconds = typeof configuredMaxAge === "number" && Number.isFinite(configuredMaxAge) && configuredMaxAge >= 0 ? Math.floor(configuredMaxAge) : DEFAULT_DOCS_MCP_CORS_MAX_AGE_SECONDS;
1581
- return {
1582
- enabled: cors !== false,
1583
- allowedHeaders: mergeHttpHeaderNames(DEFAULT_DOCS_MCP_CORS_ALLOWED_HEADERS, config.allowedHeaders),
1584
- exposedHeaders: mergeHttpHeaderNames(DEFAULT_DOCS_MCP_CORS_EXPOSED_HEADERS, config.exposedHeaders),
1585
- allowCredentials: config.allowCredentials === true,
1586
- maxAgeSeconds
1587
- };
1588
- }
1589
- function mergeHttpHeaderNames(defaults, configured) {
1590
- const headers = /* @__PURE__ */ new Map();
1591
- for (const header of [...defaults, ...configured ?? []]) {
1592
- const normalized = header.trim();
1593
- if (!/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(normalized)) continue;
1594
- const key = normalized.toLowerCase();
1595
- if (!headers.has(key)) headers.set(key, normalized);
1596
- }
1597
- return [...headers.values()];
1598
- }
1599
- function createFilesystemDocsMcpSource(options = {}) {
1600
- const rootDir = options.rootDir ?? process.cwd();
1601
- const entry = normalizePathSegment(options.entry ?? "docs") || "docs";
1602
- const contentDir = options.contentDir ?? entry;
1603
- const contentDirAbs = path.resolve(rootDir, contentDir);
1604
- const cache = /* @__PURE__ */ new Map();
1605
- const navigationCache = /* @__PURE__ */ new Map();
1606
- function getPages() {
1607
- const cached = cache.get("__default__");
1608
- if (cached) return cached;
1609
- const pages = scanFilesystemDocsPages(contentDirAbs, entry, rootDir);
1610
- cache.set("__default__", pages);
1611
- return pages;
1612
- }
1613
- function getNavigation() {
1614
- const cached = navigationCache.get("__default__");
1615
- if (cached) return cached;
1616
- const tree = buildNavigationTreeFromPages(getPages(), options.siteTitle ?? "Documentation", options.ordering);
1617
- navigationCache.set("__default__", tree);
1618
- return tree;
1619
- }
1620
- return {
1621
- entry,
1622
- siteTitle: options.siteTitle ?? "Documentation",
1623
- getPages,
1624
- getNavigation
1625
- };
1626
- }
1627
- function nowMs() {
1628
- return Date.now();
1629
- }
1630
- function durationMs(startedAt) {
1631
- return Math.max(0, Date.now() - startedAt);
1632
- }
1633
- function createStructuredTextResult(structuredContent, text) {
1634
- return {
1635
- content: [{
1636
- type: "text",
1637
- text: text ?? JSON.stringify(structuredContent, null, 2)
1638
- }],
1639
- structuredContent
1640
- };
1641
- }
1642
- async function createDocsMcpServer(options) {
1643
- const resolved = resolveDocsMcpConfig(options.mcp, {
1644
- defaultName: options.defaultName ?? options.source.siteTitle ?? DEFAULT_MCP_NAME,
1645
- defaultVersion: options.defaultVersion
1646
- });
1647
- const toolSearchConfig = resolveMcpToolSearchConfig(options.search, resolved.route);
1648
- const server = new McpServer({
1649
- name: resolved.name,
1650
- version: resolved.version
1651
- });
1652
- const telemetryConfig = {
1653
- telemetry: options.telemetry,
1654
- mcp: options.mcp,
1655
- search: options.search
1656
- };
1657
- const telemetryFramework = options.telemetryFramework ?? "mcp";
1658
- function getSourcePages(locale) {
1659
- return options.source.getPages(locale, options.requestContext);
1660
- }
1661
- function getSourceNavigation(locale) {
1662
- return options.source.getNavigation(locale, options.requestContext);
1663
- }
1664
- function trackMcpTool(tool, values) {
1665
- emitDocsTelemetryMcpToolEvent(telemetryConfig, {
1666
- framework: telemetryFramework,
1667
- request: options.requestContext?.request,
1668
- tool,
1669
- locale: values?.locale,
1670
- resultCount: values?.resultCount
1671
- });
1672
- }
1673
- const defaultPages = dedupePages(await getSourcePages());
1674
- const defaultTree = await getSourceNavigation();
1675
- server.registerResource("docs-navigation", "docs://navigation", {
1676
- title: "Docs Navigation",
1677
- description: "Structured navigation tree for the documentation site.",
1678
- mimeType: "text/plain"
1679
- }, async () => ({ contents: [{
1680
- uri: "docs://navigation",
1681
- mimeType: "text/plain",
1682
- text: renderNavigationTree(defaultTree)
1683
- }] }));
1684
- for (const page of defaultPages) {
1685
- const resourceUri = toPageResourceUri(page.url);
1686
- server.registerResource(`page-${slugToKey(page.slug)}`, resourceUri, {
1687
- title: page.title,
1688
- description: page.description,
1689
- mimeType: "text/markdown"
1690
- }, async () => ({ contents: [{
1691
- uri: resourceUri,
1692
- mimeType: "text/markdown",
1693
- text: renderPageDocument(page)
1694
- }] }));
1695
- }
1696
- if (resolved.tools.listPages) server.registerTool("list_pages", {
1697
- title: "List docs pages",
1698
- description: "List the known documentation pages with titles, slugs, and URLs.",
1699
- inputSchema: listPagesInputSchema,
1700
- outputSchema: listPagesOutputSchema,
1701
- annotations: { readOnlyHint: true }
1702
- }, async ({ locale }) => {
1703
- const startedAt = nowMs();
1704
- const trace = createDocsAgentTraceContext("mcp.tool.list_pages");
1705
- const callSpanId = createDocsAgentTraceId("span");
1706
- await emitDocsAgentTraceEvent(options.observability, {
1707
- type: "tool.call",
1708
- source: "mcp",
1709
- traceId: trace.traceId,
1710
- spanId: callSpanId,
1711
- name: "list_pages",
1712
- startedAt: trace.startedAt,
1713
- status: "started",
1714
- locale,
1715
- inputPreview: { locale },
1716
- metadata: { tool: "list_pages" }
1717
- });
1718
- try {
1719
- const pages = toPageSummaries(dedupePages(await getSourcePages(locale)));
1720
- const elapsed = durationMs(startedAt);
1721
- await emitDocsAnalyticsEvent(options.analytics, {
1722
- type: "mcp_tool",
1723
- source: "mcp",
1724
- locale,
1725
- properties: {
1726
- tool: "list_pages",
1727
- resultCount: pages.length,
1728
- durationMs: elapsed
1729
- }
1730
- });
1731
- trackMcpTool("list_pages", {
1732
- locale,
1733
- resultCount: pages.length
1734
- });
1735
- await emitDocsAgentTraceEvent(options.observability, {
1736
- type: "tool.result",
1737
- source: "mcp",
1738
- traceId: trace.traceId,
1739
- parentSpanId: callSpanId,
1740
- name: "list_pages",
1741
- startedAt: trace.startedAt,
1742
- endedAt: (/* @__PURE__ */ new Date()).toISOString(),
1743
- durationMs: elapsed,
1744
- status: "success",
1745
- locale,
1746
- outputPreview: { resultCount: pages.length },
1747
- metadata: { tool: "list_pages" }
1748
- });
1749
- return createStructuredTextResult({ pages });
1750
- } catch (error) {
1751
- const elapsed = durationMs(startedAt);
1752
- await emitDocsAgentTraceEvent(options.observability, {
1753
- type: "tool.error",
1754
- source: "mcp",
1755
- traceId: trace.traceId,
1756
- parentSpanId: callSpanId,
1757
- name: "list_pages",
1758
- startedAt: trace.startedAt,
1759
- endedAt: (/* @__PURE__ */ new Date()).toISOString(),
1760
- durationMs: elapsed,
1761
- status: "error",
1762
- locale,
1763
- outputPreview: { message: error instanceof Error ? error.message : "Unknown error" },
1764
- metadata: { tool: "list_pages" }
1765
- });
1766
- throw error;
1767
- }
1768
- });
1769
- if (resolved.tools.listDocs) server.registerTool("list_docs", {
1770
- title: "List docs by section",
1771
- description: "List documentation pages grouped by section, optionally narrowed to one section.",
1772
- inputSchema: listDocsInputSchema,
1773
- outputSchema: listDocsOutputSchema,
1774
- annotations: { readOnlyHint: true }
1775
- }, async ({ section, locale }) => {
1776
- const startedAt = nowMs();
1777
- const trace = createDocsAgentTraceContext("mcp.tool.list_docs");
1778
- const callSpanId = createDocsAgentTraceId("span");
1779
- await emitDocsAgentTraceEvent(options.observability, {
1780
- type: "tool.call",
1781
- source: "mcp",
1782
- traceId: trace.traceId,
1783
- spanId: callSpanId,
1784
- name: "list_docs",
1785
- startedAt: trace.startedAt,
1786
- status: "started",
1787
- locale,
1788
- inputPreview: {
1789
- section,
1790
- locale
1791
- },
1792
- metadata: { tool: "list_docs" }
1793
- });
1794
- try {
1795
- const docs = listDocsBySection(dedupePages(await getSourcePages(locale)), {
1796
- section,
1797
- entry: options.source.entry
1798
- });
1799
- const elapsed = durationMs(startedAt);
1800
- await emitDocsAnalyticsEvent(options.analytics, {
1801
- type: "mcp_tool",
1802
- source: "mcp",
1803
- locale,
1804
- properties: {
1805
- tool: "list_docs",
1806
- section,
1807
- resultCount: docs.resultCount,
1808
- sectionCount: docs.sectionCount,
1809
- durationMs: elapsed
1810
- }
1811
- });
1812
- trackMcpTool("list_docs", {
1813
- locale,
1814
- resultCount: docs.resultCount
1815
- });
1816
- await emitDocsAgentTraceEvent(options.observability, {
1817
- type: "tool.result",
1818
- source: "mcp",
1819
- traceId: trace.traceId,
1820
- parentSpanId: callSpanId,
1821
- name: "list_docs",
1822
- startedAt: trace.startedAt,
1823
- endedAt: (/* @__PURE__ */ new Date()).toISOString(),
1824
- durationMs: elapsed,
1825
- status: "success",
1826
- locale,
1827
- outputPreview: {
1828
- resultCount: docs.resultCount,
1829
- sectionCount: docs.sectionCount
1830
- },
1831
- metadata: { tool: "list_docs" }
1832
- });
1833
- return createStructuredTextResult(docs);
1834
- } catch (error) {
1835
- const elapsed = durationMs(startedAt);
1836
- await emitDocsAgentTraceEvent(options.observability, {
1837
- type: "tool.error",
1838
- source: "mcp",
1839
- traceId: trace.traceId,
1840
- parentSpanId: callSpanId,
1841
- name: "list_docs",
1842
- startedAt: trace.startedAt,
1843
- endedAt: (/* @__PURE__ */ new Date()).toISOString(),
1844
- durationMs: elapsed,
1845
- status: "error",
1846
- locale,
1847
- outputPreview: { message: error instanceof Error ? error.message : "Unknown error" },
1848
- metadata: { tool: "list_docs" }
1849
- });
1850
- throw error;
1851
- }
1852
- });
1853
- if (resolved.tools.listTasks) server.registerTool("list_tasks", {
1854
- title: "List documented tasks",
1855
- description: "List pages with actionable agent contracts, optionally filtered by text or applicability.",
1856
- inputSchema: listTasksInputSchema,
1857
- outputSchema: listTasksOutputSchema,
1858
- annotations: { readOnlyHint: true }
1859
- }, async ({ query, framework, version, package: packageName, locale }) => {
1860
- const startedAt = nowMs();
1861
- const trace = createDocsAgentTraceContext("mcp.tool.list_tasks");
1862
- const callSpanId = createDocsAgentTraceId("span");
1863
- await emitDocsAgentTraceEvent(options.observability, {
1864
- type: "tool.call",
1865
- source: "mcp",
1866
- traceId: trace.traceId,
1867
- spanId: callSpanId,
1868
- name: "list_tasks",
1869
- startedAt: trace.startedAt,
1870
- status: "started",
1871
- locale,
1872
- inputPreview: {
1873
- queryLength: query?.length,
1874
- framework,
1875
- version,
1876
- package: packageName
1877
- },
1878
- metadata: { tool: "list_tasks" }
1879
- });
1880
- try {
1881
- const tasks = listDocsTasks(dedupePages(await getSourcePages(locale)), {
1882
- query,
1883
- framework,
1884
- version,
1885
- package: packageName
1886
- });
1887
- const result = {
1888
- resultCount: tasks.length,
1889
- tasks
1890
- };
1891
- const elapsed = durationMs(startedAt);
1892
- await emitDocsAnalyticsEvent(options.analytics, {
1893
- type: "mcp_tool",
1894
- source: "mcp",
1895
- locale,
1896
- input: query ? { query } : void 0,
1897
- properties: {
1898
- tool: "list_tasks",
1899
- framework,
1900
- version,
1901
- package: packageName,
1902
- resultCount: tasks.length,
1903
- durationMs: elapsed
1904
- }
1905
- });
1906
- trackMcpTool("list_tasks", {
1907
- locale,
1908
- resultCount: tasks.length
1909
- });
1910
- await emitDocsAgentTraceEvent(options.observability, {
1911
- type: "tool.result",
1912
- source: "mcp",
1913
- traceId: trace.traceId,
1914
- parentSpanId: callSpanId,
1915
- name: "list_tasks",
1916
- startedAt: trace.startedAt,
1917
- endedAt: (/* @__PURE__ */ new Date()).toISOString(),
1918
- durationMs: elapsed,
1919
- status: "success",
1920
- locale,
1921
- outputPreview: { resultCount: tasks.length },
1922
- metadata: { tool: "list_tasks" }
1923
- });
1924
- return {
1925
- structuredContent: result,
1926
- content: [{
1927
- type: "text",
1928
- text: JSON.stringify(result, null, 2)
1929
- }]
1930
- };
1931
- } catch (error) {
1932
- const elapsed = durationMs(startedAt);
1933
- await emitDocsAgentTraceEvent(options.observability, {
1934
- type: "tool.error",
1935
- source: "mcp",
1936
- traceId: trace.traceId,
1937
- parentSpanId: callSpanId,
1938
- name: "list_tasks",
1939
- startedAt: trace.startedAt,
1940
- endedAt: (/* @__PURE__ */ new Date()).toISOString(),
1941
- durationMs: elapsed,
1942
- status: "error",
1943
- locale,
1944
- outputPreview: { message: error instanceof Error ? error.message : "Unknown error" },
1945
- metadata: { tool: "list_tasks" }
1946
- });
1947
- throw error;
1948
- }
1949
- });
1950
- if (resolved.tools.readTask) server.registerTool("read_task", {
1951
- title: "Read a documented task",
1952
- description: "Read the full structured agent contract for a page by slug or URL path.",
1953
- inputSchema: readTaskInputSchema,
1954
- outputSchema: readTaskOutputSchema,
1955
- annotations: { readOnlyHint: true }
1956
- }, async ({ path: requestedPath, locale }) => {
1957
- const startedAt = nowMs();
1958
- const trace = createDocsAgentTraceContext("mcp.tool.read_task");
1959
- const callSpanId = createDocsAgentTraceId("span");
1960
- await emitDocsAgentTraceEvent(options.observability, {
1961
- type: "tool.call",
1962
- source: "mcp",
1963
- traceId: trace.traceId,
1964
- spanId: callSpanId,
1965
- name: "read_task",
1966
- startedAt: trace.startedAt,
1967
- status: "started",
1968
- locale,
1969
- inputPreview: {
1970
- path: requestedPath,
1971
- locale
1972
- },
1973
- metadata: { tool: "read_task" }
1974
- });
1975
- try {
1976
- const page = findDocsPage(dedupePages(await getSourcePages(locale)), requestedPath, options.source.entry);
1977
- const contract = normalizePageAgentFrontmatter(page?.agent);
1978
- if (!page || !contract || !hasStructuredPageAgentContract(contract)) {
1979
- const elapsed = durationMs(startedAt);
1980
- const reason = page ? "contract_not_found" : "page_not_found";
1981
- const errorResult = { error: page ? `The docs page matched "${requestedPath}", but it has no actionable agent contract.` : `No docs page matched "${requestedPath}".` };
1982
- await emitDocsAnalyticsEvent(options.analytics, {
1983
- type: "mcp_tool",
1984
- source: "mcp",
1985
- locale,
1986
- properties: {
1987
- tool: "read_task",
1988
- path: requestedPath,
1989
- found: false,
1990
- reason,
1991
- durationMs: elapsed
1992
- }
1993
- });
1994
- trackMcpTool("read_task", {
1995
- locale,
1996
- resultCount: 0
1997
- });
1998
- await emitDocsAgentTraceEvent(options.observability, {
1999
- type: "tool.error",
2000
- source: "mcp",
2001
- traceId: trace.traceId,
2002
- parentSpanId: callSpanId,
2003
- name: "read_task",
2004
- startedAt: trace.startedAt,
2005
- endedAt: (/* @__PURE__ */ new Date()).toISOString(),
2006
- durationMs: elapsed,
2007
- status: "error",
2008
- locale,
2009
- outputPreview: {
2010
- found: false,
2011
- path: requestedPath
2012
- },
2013
- metadata: {
2014
- tool: "read_task",
2015
- reason
2016
- }
2017
- });
2018
- return {
2019
- content: [{
2020
- type: "text",
2021
- text: JSON.stringify(errorResult, null, 2)
2022
- }],
2023
- isError: true
2024
- };
2025
- }
2026
- const result = {
2027
- page: {
2028
- slug: page.slug,
2029
- url: page.url,
2030
- title: page.title,
2031
- ...page.description ? { description: page.description } : {},
2032
- ...page.sourcePath ? { sourcePath: page.sourcePath } : {},
2033
- ...page.lastModified ? { lastModified: page.lastModified } : {}
2034
- },
2035
- contract
2036
- };
2037
- const elapsed = durationMs(startedAt);
2038
- await emitDocsAnalyticsEvent(options.analytics, {
2039
- type: "mcp_tool",
2040
- source: "mcp",
2041
- locale,
2042
- path: page.url,
2043
- properties: {
2044
- tool: "read_task",
2045
- requestedPath,
2046
- slug: page.slug,
2047
- found: true,
2048
- durationMs: elapsed
2049
- }
2050
- });
2051
- trackMcpTool("read_task", {
2052
- locale,
2053
- resultCount: 1
2054
- });
2055
- await emitDocsAgentTraceEvent(options.observability, {
2056
- type: "tool.result",
2057
- source: "mcp",
2058
- traceId: trace.traceId,
2059
- parentSpanId: callSpanId,
2060
- name: "read_task",
2061
- startedAt: trace.startedAt,
2062
- endedAt: (/* @__PURE__ */ new Date()).toISOString(),
2063
- durationMs: elapsed,
2064
- status: "success",
2065
- locale,
2066
- path: page.url,
2067
- outputPreview: {
2068
- found: true,
2069
- slug: page.slug
2070
- },
2071
- metadata: { tool: "read_task" }
2072
- });
2073
- return {
2074
- structuredContent: result,
2075
- content: [{
2076
- type: "text",
2077
- text: JSON.stringify(result, null, 2)
2078
- }]
2079
- };
2080
- } catch (error) {
2081
- const elapsed = durationMs(startedAt);
2082
- await emitDocsAgentTraceEvent(options.observability, {
2083
- type: "tool.error",
2084
- source: "mcp",
2085
- traceId: trace.traceId,
2086
- parentSpanId: callSpanId,
2087
- name: "read_task",
2088
- startedAt: trace.startedAt,
2089
- endedAt: (/* @__PURE__ */ new Date()).toISOString(),
2090
- durationMs: elapsed,
2091
- status: "error",
2092
- locale,
2093
- outputPreview: { message: error instanceof Error ? error.message : "Unknown error" },
2094
- metadata: { tool: "read_task" }
2095
- });
2096
- throw error;
2097
- }
2098
- });
2099
- if (resolved.tools.getNavigation) server.registerTool("get_navigation", {
2100
- title: "Get docs navigation",
2101
- description: "Return the documentation navigation tree for the current docs site.",
2102
- inputSchema: getNavigationInputSchema,
2103
- outputSchema: navigationOutputSchema,
2104
- annotations: { readOnlyHint: true }
2105
- }, async ({ locale }) => {
2106
- const startedAt = nowMs();
2107
- const trace = createDocsAgentTraceContext("mcp.tool.get_navigation");
2108
- const callSpanId = createDocsAgentTraceId("span");
2109
- await emitDocsAgentTraceEvent(options.observability, {
2110
- type: "tool.call",
2111
- source: "mcp",
2112
- traceId: trace.traceId,
2113
- spanId: callSpanId,
2114
- name: "get_navigation",
2115
- startedAt: trace.startedAt,
2116
- status: "started",
2117
- locale,
2118
- inputPreview: { locale },
2119
- metadata: { tool: "get_navigation" }
2120
- });
2121
- try {
2122
- const tree = await getSourceNavigation(locale);
2123
- const text = renderNavigationTree(tree);
2124
- const elapsed = durationMs(startedAt);
2125
- await emitDocsAnalyticsEvent(options.analytics, {
2126
- type: "mcp_tool",
2127
- source: "mcp",
2128
- locale,
2129
- properties: {
2130
- tool: "get_navigation",
2131
- durationMs: elapsed
2132
- }
2133
- });
2134
- trackMcpTool("get_navigation", { locale });
2135
- await emitDocsAgentTraceEvent(options.observability, {
2136
- type: "tool.result",
2137
- source: "mcp",
2138
- traceId: trace.traceId,
2139
- parentSpanId: callSpanId,
2140
- name: "get_navigation",
2141
- startedAt: trace.startedAt,
2142
- endedAt: (/* @__PURE__ */ new Date()).toISOString(),
2143
- durationMs: elapsed,
2144
- status: "success",
2145
- locale,
2146
- outputPreview: { chars: text.length },
2147
- metadata: { tool: "get_navigation" }
2148
- });
2149
- return createStructuredTextResult({
2150
- navigation: tree,
2151
- markdown: text
2152
- }, text);
2153
- } catch (error) {
2154
- const elapsed = durationMs(startedAt);
2155
- await emitDocsAgentTraceEvent(options.observability, {
2156
- type: "tool.error",
2157
- source: "mcp",
2158
- traceId: trace.traceId,
2159
- parentSpanId: callSpanId,
2160
- name: "get_navigation",
2161
- startedAt: trace.startedAt,
2162
- endedAt: (/* @__PURE__ */ new Date()).toISOString(),
2163
- durationMs: elapsed,
2164
- status: "error",
2165
- locale,
2166
- outputPreview: { message: error instanceof Error ? error.message : "Unknown error" },
2167
- metadata: { tool: "get_navigation" }
2168
- });
2169
- throw error;
2170
- }
2171
- });
2172
- if (resolved.tools.getConfigSchema) server.registerTool("get_config_schema", {
2173
- title: "Get docs config schema",
2174
- description: "Return structured docs.config.ts option metadata, optionally filtered by option path or query.",
2175
- inputSchema: getConfigSchemaInputSchema,
2176
- outputSchema: configSchemaOutputSchema,
2177
- annotations: { readOnlyHint: true }
2178
- }, async ({ option, query }) => {
2179
- const startedAt = nowMs();
2180
- const trace = createDocsAgentTraceContext("mcp.tool.get_config_schema");
2181
- const callSpanId = createDocsAgentTraceId("span");
2182
- await emitDocsAgentTraceEvent(options.observability, {
2183
- type: "tool.call",
2184
- source: "mcp",
2185
- traceId: trace.traceId,
2186
- spanId: callSpanId,
2187
- name: "get_config_schema",
2188
- startedAt: trace.startedAt,
2189
- status: "started",
2190
- inputPreview: {
2191
- option,
2192
- queryLength: query?.length
2193
- },
2194
- metadata: { tool: "get_config_schema" }
2195
- });
2196
- try {
2197
- const schema = getDocsConfigSchema({
2198
- option,
2199
- query
2200
- });
2201
- const elapsed = durationMs(startedAt);
2202
- await emitDocsAnalyticsEvent(options.analytics, {
2203
- type: "mcp_tool",
2204
- source: "mcp",
2205
- input: query ? { query } : void 0,
2206
- properties: {
2207
- tool: "get_config_schema",
2208
- option,
2209
- queryLength: query?.length,
2210
- resultCount: schema.resultCount,
2211
- durationMs: elapsed
2212
- }
2213
- });
2214
- trackMcpTool("get_config_schema", { resultCount: schema.resultCount });
2215
- await emitDocsAgentTraceEvent(options.observability, {
2216
- type: "tool.result",
2217
- source: "mcp",
2218
- traceId: trace.traceId,
2219
- parentSpanId: callSpanId,
2220
- name: "get_config_schema",
2221
- startedAt: trace.startedAt,
2222
- endedAt: (/* @__PURE__ */ new Date()).toISOString(),
2223
- durationMs: elapsed,
2224
- status: "success",
2225
- outputPreview: { resultCount: schema.resultCount },
2226
- metadata: { tool: "get_config_schema" }
2227
- });
2228
- return createStructuredTextResult(schema);
2229
- } catch (error) {
2230
- const elapsed = durationMs(startedAt);
2231
- await emitDocsAgentTraceEvent(options.observability, {
2232
- type: "tool.error",
2233
- source: "mcp",
2234
- traceId: trace.traceId,
2235
- parentSpanId: callSpanId,
2236
- name: "get_config_schema",
2237
- startedAt: trace.startedAt,
2238
- endedAt: (/* @__PURE__ */ new Date()).toISOString(),
2239
- durationMs: elapsed,
2240
- status: "error",
2241
- outputPreview: { message: error instanceof Error ? error.message : "Unknown error" },
2242
- metadata: { tool: "get_config_schema" }
2243
- });
2244
- throw error;
2245
- }
2246
- });
2247
- if (resolved.tools.searchDocs) server.registerTool("search_docs", {
2248
- title: "Search documentation",
2249
- description: "Search the docs by keyword across titles, descriptions, and page content.",
2250
- inputSchema: searchDocsInputSchema,
2251
- outputSchema: searchDocsOutputSchema,
2252
- annotations: { readOnlyHint: true }
2253
- }, async ({ query, limit, locale }) => {
2254
- const startedAt = nowMs();
2255
- const resolvedLimit = limit ?? 10;
2256
- const trace = createDocsAgentTraceContext("mcp.tool.search_docs");
2257
- const callSpanId = createDocsAgentTraceId("span");
2258
- await emitDocsAgentTraceEvent(options.observability, {
2259
- type: "tool.call",
2260
- source: "mcp",
2261
- traceId: trace.traceId,
2262
- spanId: callSpanId,
2263
- name: "search_docs",
2264
- startedAt: trace.startedAt,
2265
- status: "started",
2266
- locale,
2267
- inputPreview: {
2268
- queryLength: query.length,
2269
- limit: resolvedLimit,
2270
- locale
2271
- },
2272
- metadata: { tool: "search_docs" }
2273
- });
2274
- try {
2275
- const results = await performDocsSearch({
2276
- pages: toSearchSourcePages(dedupePages(await getSourcePages(locale))),
2277
- query,
2278
- search: toolSearchConfig ?? true,
2279
- audience: "agent",
2280
- locale,
2281
- siteTitle: options.source.siteTitle,
2282
- limit: resolvedLimit
2283
- });
2284
- const elapsed = durationMs(startedAt);
2285
- await emitDocsAnalyticsEvent(options.analytics, {
2286
- type: "mcp_tool",
2287
- source: "mcp",
2288
- locale,
2289
- input: { query },
2290
- properties: {
2291
- tool: "search_docs",
2292
- queryLength: query.length,
2293
- limit: resolvedLimit,
2294
- resultCount: results.length,
2295
- durationMs: elapsed
2296
- }
2297
- });
2298
- trackMcpTool("search_docs", {
2299
- locale,
2300
- resultCount: results.length
2301
- });
2302
- await emitDocsAgentTraceEvent(options.observability, {
2303
- type: "tool.result",
2304
- source: "mcp",
2305
- traceId: trace.traceId,
2306
- parentSpanId: callSpanId,
2307
- name: "search_docs",
2308
- startedAt: trace.startedAt,
2309
- endedAt: (/* @__PURE__ */ new Date()).toISOString(),
2310
- durationMs: elapsed,
2311
- status: "success",
2312
- locale,
2313
- outputPreview: { resultCount: results.length },
2314
- metadata: { tool: "search_docs" }
2315
- });
2316
- return createStructuredTextResult({ results });
2317
- } catch (error) {
2318
- const elapsed = durationMs(startedAt);
2319
- await emitDocsAgentTraceEvent(options.observability, {
2320
- type: "tool.error",
2321
- source: "mcp",
2322
- traceId: trace.traceId,
2323
- parentSpanId: callSpanId,
2324
- name: "search_docs",
2325
- startedAt: trace.startedAt,
2326
- endedAt: (/* @__PURE__ */ new Date()).toISOString(),
2327
- durationMs: elapsed,
2328
- status: "error",
2329
- locale,
2330
- outputPreview: { message: error instanceof Error ? error.message : "Unknown error" },
2331
- metadata: { tool: "search_docs" }
2332
- });
2333
- throw error;
2334
- }
2335
- });
2336
- if (resolved.tools.getCodeExamples) server.registerTool("get_code_examples", {
2337
- title: "Get docs code examples",
2338
- description: "Return fenced code examples from the docs with parsed metadata such as title, framework, packageManager, and runnable.",
2339
- inputSchema: getCodeExamplesInputSchema,
2340
- outputSchema: codeExamplesOutputSchema,
2341
- annotations: { readOnlyHint: true }
2342
- }, async ({ query, path: requestedPath, framework, packageManager, language, runnable, limit, locale }) => {
2343
- const startedAt = nowMs();
2344
- const resolvedLimit = limit ?? 25;
2345
- const trace = createDocsAgentTraceContext("mcp.tool.get_code_examples");
2346
- const callSpanId = createDocsAgentTraceId("span");
2347
- await emitDocsAgentTraceEvent(options.observability, {
2348
- type: "tool.call",
2349
- source: "mcp",
2350
- traceId: trace.traceId,
2351
- spanId: callSpanId,
2352
- name: "get_code_examples",
2353
- startedAt: trace.startedAt,
2354
- status: "started",
2355
- locale,
2356
- inputPreview: {
2357
- queryLength: query?.length,
2358
- path: requestedPath,
2359
- framework,
2360
- packageManager,
2361
- language,
2362
- runnable,
2363
- limit: resolvedLimit
2364
- },
2365
- metadata: { tool: "get_code_examples" }
2366
- });
2367
- try {
2368
- const pages = dedupePages(await getSourcePages(locale));
2369
- const matchedPage = requestedPath ? findDocsPage(pages, requestedPath, options.source.entry) : null;
2370
- const examples = filterDocsCodeExamples((requestedPath ? matchedPage ? [matchedPage] : [] : pages).flatMap((page) => extractDocsMcpCodeExamples(page)), {
2371
- query,
2372
- framework,
2373
- packageManager,
2374
- language,
2375
- runnable,
2376
- limit: resolvedLimit
2377
- });
2378
- const elapsed = durationMs(startedAt);
2379
- await emitDocsAnalyticsEvent(options.analytics, {
2380
- type: "mcp_tool",
2381
- source: "mcp",
2382
- locale,
2383
- input: query ? { query } : void 0,
2384
- properties: {
2385
- tool: "get_code_examples",
2386
- queryLength: query?.length,
2387
- path: requestedPath,
2388
- framework,
2389
- packageManager,
2390
- language,
2391
- runnable,
2392
- limit: resolvedLimit,
2393
- resultCount: examples.length,
2394
- durationMs: elapsed
2395
- }
2396
- });
2397
- trackMcpTool("get_code_examples", {
2398
- locale,
2399
- resultCount: examples.length
2400
- });
2401
- await emitDocsAgentTraceEvent(options.observability, {
2402
- type: "tool.result",
2403
- source: "mcp",
2404
- traceId: trace.traceId,
2405
- parentSpanId: callSpanId,
2406
- name: "get_code_examples",
2407
- startedAt: trace.startedAt,
2408
- endedAt: (/* @__PURE__ */ new Date()).toISOString(),
2409
- durationMs: elapsed,
2410
- status: "success",
2411
- locale,
2412
- outputPreview: { resultCount: examples.length },
2413
- metadata: { tool: "get_code_examples" }
2414
- });
2415
- return createStructuredTextResult({ examples });
2416
- } catch (error) {
2417
- const elapsed = durationMs(startedAt);
2418
- await emitDocsAgentTraceEvent(options.observability, {
2419
- type: "tool.error",
2420
- source: "mcp",
2421
- traceId: trace.traceId,
2422
- parentSpanId: callSpanId,
2423
- name: "get_code_examples",
2424
- startedAt: trace.startedAt,
2425
- endedAt: (/* @__PURE__ */ new Date()).toISOString(),
2426
- durationMs: elapsed,
2427
- status: "error",
2428
- locale,
2429
- outputPreview: { message: error instanceof Error ? error.message : "Unknown error" },
2430
- metadata: { tool: "get_code_examples" }
2431
- });
2432
- throw error;
2433
- }
2434
- });
2435
- if (resolved.tools.getContext) server.registerTool("get_context", {
2436
- title: "Get budgeted docs context",
2437
- 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.",
2438
- inputSchema: getContextInputSchema,
2439
- outputSchema: contextOutputSchema,
2440
- annotations: { readOnlyHint: true }
2441
- }, async ({ query, framework, version, locale, tokenBudget }) => {
2442
- const startedAt = nowMs();
2443
- const trace = createDocsAgentTraceContext("mcp.tool.get_context");
2444
- const callSpanId = createDocsAgentTraceId("span");
2445
- await emitDocsAgentTraceEvent(options.observability, {
2446
- type: "tool.call",
2447
- source: "mcp",
2448
- traceId: trace.traceId,
2449
- spanId: callSpanId,
2450
- name: "get_context",
2451
- startedAt: trace.startedAt,
2452
- status: "started",
2453
- locale,
2454
- inputPreview: {
2455
- queryLength: query.length,
2456
- framework,
2457
- version,
2458
- locale,
2459
- tokenBudget
2460
- },
2461
- metadata: { tool: "get_context" }
2462
- });
2463
- try {
2464
- const result = await buildDocsMcpContext({
2465
- pages: dedupePages(await getSourcePages(locale)),
2466
- query,
2467
- framework,
2468
- version,
2469
- locale,
2470
- tokenBudget,
2471
- entry: options.source.entry,
2472
- siteTitle: options.source.siteTitle
2473
- });
2474
- const elapsed = durationMs(startedAt);
2475
- await emitDocsAnalyticsEvent(options.analytics, {
2476
- type: "mcp_tool",
2477
- source: "mcp",
2478
- locale,
2479
- input: { query },
2480
- properties: {
2481
- tool: "get_context",
2482
- queryLength: query.length,
2483
- framework,
2484
- version,
2485
- tokenBudget,
2486
- usedUtf8Bytes: result.budget.usedUtf8Bytes,
2487
- conservativeTokenUpperBound: result.budget.conservativeTokenUpperBound,
2488
- truncated: result.budget.truncated,
2489
- resultCount: result.resultCount,
2490
- durationMs: elapsed
2491
- }
2492
- });
2493
- trackMcpTool("get_context", {
2494
- locale,
2495
- resultCount: result.resultCount
2496
- });
2497
- await emitDocsAgentTraceEvent(options.observability, {
2498
- type: "tool.result",
2499
- source: "mcp",
2500
- traceId: trace.traceId,
2501
- parentSpanId: callSpanId,
2502
- name: "get_context",
2503
- startedAt: trace.startedAt,
2504
- endedAt: (/* @__PURE__ */ new Date()).toISOString(),
2505
- durationMs: elapsed,
2506
- status: "success",
2507
- locale,
2508
- outputPreview: {
2509
- resultCount: result.resultCount,
2510
- candidateCount: result.candidateCount,
2511
- utf8Bytes: result.budget.usedUtf8Bytes,
2512
- conservativeTokenUpperBound: result.budget.conservativeTokenUpperBound,
2513
- truncated: result.budget.truncated
2514
- },
2515
- metadata: { tool: "get_context" }
2516
- });
2517
- return createStructuredTextResult(result, result.context || JSON.stringify(result, null, 2));
2518
- } catch (error) {
2519
- const elapsed = durationMs(startedAt);
2520
- await emitDocsAgentTraceEvent(options.observability, {
2521
- type: "tool.error",
2522
- source: "mcp",
2523
- traceId: trace.traceId,
2524
- parentSpanId: callSpanId,
2525
- name: "get_context",
2526
- startedAt: trace.startedAt,
2527
- endedAt: (/* @__PURE__ */ new Date()).toISOString(),
2528
- durationMs: elapsed,
2529
- status: "error",
2530
- locale,
2531
- outputPreview: { message: error instanceof Error ? error.message : "Unknown error" },
2532
- metadata: { tool: "get_context" }
2533
- });
2534
- throw error;
2535
- }
2536
- });
2537
- if (resolved.tools.readPage) server.registerTool("read_page", {
2538
- title: "Read a docs page",
2539
- description: "Read a documentation page by slug or URL path, optionally selecting one heading and limiting returned characters.",
2540
- inputSchema: readPageInputSchema,
2541
- outputSchema: readPageOutputSchema,
2542
- annotations: { readOnlyHint: true }
2543
- }, async ({ path: requestedPath, locale, section, maxChars }) => {
2544
- const startedAt = nowMs();
2545
- const trace = createDocsAgentTraceContext("mcp.tool.read_page");
2546
- const callSpanId = createDocsAgentTraceId("span");
2547
- await emitDocsAgentTraceEvent(options.observability, {
2548
- type: "tool.call",
2549
- source: "mcp",
2550
- traceId: trace.traceId,
2551
- spanId: callSpanId,
2552
- name: "read_page",
2553
- startedAt: trace.startedAt,
2554
- status: "started",
2555
- locale,
2556
- inputPreview: {
2557
- path: requestedPath,
2558
- locale,
2559
- section,
2560
- maxChars
2561
- },
2562
- metadata: { tool: "read_page" }
2563
- });
2564
- try {
2565
- const page = findDocsPage(dedupePages(await getSourcePages(locale)), requestedPath, options.source.entry);
2566
- if (!page) {
2567
- const elapsed = durationMs(startedAt);
2568
- await emitDocsAnalyticsEvent(options.analytics, {
2569
- type: "agent_read",
2570
- source: "mcp",
2571
- locale,
2572
- properties: {
2573
- delivery: "mcp_tool",
2574
- tool: "read_page",
2575
- requestedPath,
2576
- found: false,
2577
- durationMs: elapsed
2578
- }
2579
- });
2580
- await emitDocsAnalyticsEvent(options.analytics, {
2581
- type: "mcp_tool",
2582
- source: "mcp",
2583
- locale,
2584
- properties: {
2585
- tool: "read_page",
2586
- path: requestedPath,
2587
- found: false,
2588
- durationMs: elapsed
2589
- }
2590
- });
2591
- trackMcpTool("read_page", {
2592
- locale,
2593
- resultCount: 0
2594
- });
2595
- await emitDocsAgentTraceEvent(options.observability, {
2596
- type: "tool.error",
2597
- source: "mcp",
2598
- traceId: trace.traceId,
2599
- parentSpanId: callSpanId,
2600
- name: "read_page",
2601
- startedAt: trace.startedAt,
2602
- endedAt: (/* @__PURE__ */ new Date()).toISOString(),
2603
- durationMs: elapsed,
2604
- status: "error",
2605
- locale,
2606
- outputPreview: {
2607
- found: false,
2608
- path: requestedPath
2609
- },
2610
- metadata: {
2611
- tool: "read_page",
2612
- reason: "not_found"
2613
- }
2614
- });
2615
- return {
2616
- content: [{
2617
- type: "text",
2618
- text: JSON.stringify({ error: `No docs page matched "${requestedPath}".` }, null, 2)
2619
- }],
2620
- isError: true
2621
- };
2622
- }
2623
- const fullDocument = renderPageDocument(page);
2624
- const sectionDocument = getDocsMcpSourceMarkdown(page);
2625
- const selectedSection = section ? findDocsMarkdownSection(sectionDocument, section) ?? (sectionDocument !== fullDocument ? findDocsMarkdownSection(fullDocument, section) : void 0) : void 0;
2626
- if (section && !selectedSection) {
2627
- const sourceSections = parseDocsMarkdownSections(sectionDocument);
2628
- const availableSections = (sourceSections.length > 0 ? sourceSections : parseDocsMarkdownSections(fullDocument)).map((item) => ({
2629
- title: item.title,
2630
- anchor: item.anchor
2631
- }));
2632
- const errorText = renderBoundedDocsMcpSectionError({
2633
- requestedSection: section,
2634
- pageUrl: page.url,
2635
- availableSections,
2636
- maxChars
2637
- });
2638
- const elapsed = durationMs(startedAt);
2639
- trackMcpTool("read_page", {
2640
- locale,
2641
- resultCount: 0
2642
- });
2643
- await emitDocsAgentTraceEvent(options.observability, {
2644
- type: "tool.error",
2645
- source: "mcp",
2646
- traceId: trace.traceId,
2647
- parentSpanId: callSpanId,
2648
- name: "read_page",
2649
- startedAt: trace.startedAt,
2650
- endedAt: (/* @__PURE__ */ new Date()).toISOString(),
2651
- durationMs: elapsed,
2652
- status: "error",
2653
- locale,
2654
- path: page.url,
2655
- outputPreview: {
2656
- found: true,
2657
- sectionFound: false,
2658
- section
2659
- },
2660
- metadata: {
2661
- tool: "read_page",
2662
- reason: "section_not_found"
2663
- }
2664
- });
2665
- return {
2666
- content: [{
2667
- type: "text",
2668
- text: errorText
2669
- }],
2670
- isError: true
2671
- };
2672
- }
2673
- const selectedDocument = selectedSection?.content ?? fullDocument;
2674
- const limitedDocument = limitDocsMcpText(selectedDocument, maxChars);
2675
- const document = limitedDocument.text;
2676
- const elapsed = durationMs(startedAt);
2677
- await emitDocsAnalyticsEvent(options.analytics, {
2678
- type: "agent_read",
2679
- source: "mcp",
2680
- locale,
2681
- path: page.url,
2682
- properties: {
2683
- delivery: "mcp_tool",
2684
- tool: "read_page",
2685
- requestedPath,
2686
- slug: page.slug,
2687
- found: true,
2688
- section: selectedSection?.title,
2689
- contentLength: document.length,
2690
- totalContentLength: selectedDocument.length,
2691
- truncated: limitedDocument.truncated,
2692
- durationMs: elapsed
2693
- }
2694
- });
2695
- await emitDocsAnalyticsEvent(options.analytics, {
2696
- type: "mcp_tool",
2697
- source: "mcp",
2698
- locale,
2699
- path: page.url,
2700
- properties: {
2701
- tool: "read_page",
2702
- requestedPath,
2703
- slug: page.slug,
2704
- found: true,
2705
- section: selectedSection?.title,
2706
- contentLength: document.length,
2707
- totalContentLength: selectedDocument.length,
2708
- truncated: limitedDocument.truncated,
2709
- durationMs: elapsed
2710
- }
2711
- });
2712
- trackMcpTool("read_page", {
2713
- locale,
2714
- resultCount: 1
2715
- });
2716
- await emitDocsAgentTraceEvent(options.observability, {
2717
- type: "tool.result",
2718
- source: "mcp",
2719
- traceId: trace.traceId,
2720
- parentSpanId: callSpanId,
2721
- name: "read_page",
2722
- startedAt: trace.startedAt,
2723
- endedAt: (/* @__PURE__ */ new Date()).toISOString(),
2724
- durationMs: elapsed,
2725
- status: "success",
2726
- locale,
2727
- path: page.url,
2728
- outputPreview: {
2729
- found: true,
2730
- chars: document.length,
2731
- totalChars: selectedDocument.length,
2732
- truncated: limitedDocument.truncated,
2733
- section: selectedSection?.title,
2734
- slug: page.slug
2735
- },
2736
- metadata: { tool: "read_page" }
2737
- });
2738
- return createStructuredTextResult({
2739
- page: toStructuredDocsMcpPage(page),
2740
- document,
2741
- section: selectedSection?.title,
2742
- anchor: selectedSection?.anchor,
2743
- chars: document.length,
2744
- totalChars: selectedDocument.length,
2745
- truncated: limitedDocument.truncated
2746
- }, document);
2747
- } catch (error) {
2748
- const elapsed = durationMs(startedAt);
2749
- await emitDocsAgentTraceEvent(options.observability, {
2750
- type: "tool.error",
2751
- source: "mcp",
2752
- traceId: trace.traceId,
2753
- parentSpanId: callSpanId,
2754
- name: "read_page",
2755
- startedAt: trace.startedAt,
2756
- endedAt: (/* @__PURE__ */ new Date()).toISOString(),
2757
- durationMs: elapsed,
2758
- status: "error",
2759
- locale,
2760
- outputPreview: { message: error instanceof Error ? error.message : "Unknown error" },
2761
- metadata: { tool: "read_page" }
2762
- });
2763
- throw error;
2764
- }
2765
- });
2766
- return server;
2767
- }
2768
- function createDocsMcpHttpHandler(options) {
2769
- const resolved = resolveDocsMcpConfig(options.mcp, {
2770
- defaultName: options.defaultName ?? options.source.siteTitle ?? DEFAULT_MCP_NAME,
2771
- defaultVersion: options.defaultVersion
2772
- });
2773
- const telemetryConfig = {
2774
- telemetry: options.telemetry,
2775
- mcp: options.mcp,
2776
- search: options.search
2777
- };
2778
- const telemetryFramework = options.telemetryFramework ?? "mcp";
2779
- const disabledMessage = "MCP is disabled. Remove `mcp: false` or set `mcp: { enabled: true }` in docs.config to enable it again.";
2780
- if (!resolved.enabled) return {
2781
- GET: async () => createJsonErrorResponse(404, disabledMessage),
2782
- POST: async ({ request }) => createJsonRpcErrorResponse({
2783
- status: 404,
2784
- code: -32e3,
2785
- message: disabledMessage,
2786
- id: readJsonRpcId(await parseJsonBody(request)),
2787
- data: { reason: "mcp_disabled" }
2788
- }),
2789
- DELETE: async () => createJsonErrorResponse(404, disabledMessage),
2790
- OPTIONS: async () => createJsonErrorResponse(404, disabledMessage)
2791
- };
2792
- async function createStatelessTransport(requestContext) {
2793
- const server = await createDocsMcpServer({
2794
- ...options,
2795
- requestContext
2796
- });
2797
- const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
2798
- await server.connect(transport);
2799
- return {
2800
- server,
2801
- transport
2802
- };
2803
- }
2804
- async function handle(request) {
2805
- const url = new URL(request.url);
2806
- const method = request.method.toUpperCase();
2807
- const security = resolved.security ?? resolveDocsMcpSecurityConfig();
2808
- const prepared = await prepareDocsMcpHttpRequest(request, security.maxBodyBytes);
2809
- if (prepared.status === "too-large") return createMcpRequestTooLargeResponse(security.maxBodyBytes);
2810
- request = prepared.request;
2811
- let originAllowed;
2812
- try {
2813
- originAllowed = await isDocsMcpOriginAllowed(request.clone(), security.allowedOrigins);
2814
- } catch {
2815
- return createMcpHttpSecurityErrorResponse(500, "MCP Origin policy failed");
2816
- }
2817
- if (!originAllowed) return createMcpHttpSecurityErrorResponse(403, "Forbidden Origin");
2818
- if (method === "OPTIONS") return createDocsMcpOptionsResponse(request, security.cors);
2819
- const withCors = (response) => applyDocsMcpCorsHeaders(response, request, security.cors);
2820
- let auth;
2821
- if (security.authenticate) {
2822
- let authentication;
2823
- try {
2824
- authentication = await security.authenticate({
2825
- request: request.clone(),
2826
- pathname: url.pathname
2827
- });
2828
- } catch {
2829
- return withCors(createMcpHttpSecurityErrorResponse(500, "MCP authentication failed"));
2830
- }
2831
- if (authentication instanceof Response) return withCors(authentication);
2832
- if (authentication === null || authentication === void 0) return withCors(createMcpHttpSecurityErrorResponse(401, "Unauthorized"));
2833
- if (!isDocsMcpAuthPrincipal(authentication)) return withCors(createMcpHttpSecurityErrorResponse(500, "MCP authentication returned an invalid principal"));
2834
- auth = authentication;
2835
- }
2836
- const sessionId = request.headers.get("mcp-session-id") ?? request.headers.get("Mcp-Session-Id");
2837
- const parsedBody = prepared.parsedBody;
2838
- const bodyParseFailed = prepared.bodyParseFailed;
2839
- const initializeRequest = method === "POST" && parsedBody && isInitializeRequest(parsedBody);
2840
- emitDocsTelemetryProjectEvent(telemetryConfig, {
2841
- framework: telemetryFramework,
2842
- request
2843
- });
2844
- emitDocsTelemetryAgentSurfaceEvent(telemetryConfig, {
2845
- framework: telemetryFramework,
2846
- request,
2847
- surface: "mcp",
2848
- properties: {
2849
- method,
2850
- initialize: Boolean(initializeRequest)
2851
- }
2852
- });
2853
- await emitDocsAnalyticsEvent(options.analytics, {
2854
- type: "mcp_request",
2855
- source: "mcp",
2856
- url: request.url,
2857
- path: url.pathname,
2858
- properties: {
2859
- method,
2860
- hasSession: Boolean(sessionId),
2861
- stateless: true,
2862
- initialize: Boolean(initializeRequest)
2863
- }
2864
- });
2865
- if (method === "POST" && bodyParseFailed) return withCors(createJsonRpcErrorResponse({
2866
- status: 400,
2867
- code: -32700,
2868
- message: "Parse error: Invalid JSON"
2869
- }));
2870
- return withCors(await (await createStatelessTransport({
2871
- transport: "http",
2872
- request: request.clone(),
2873
- auth
2874
- })).transport.handleRequest(request, parsedBody === void 0 ? void 0 : { parsedBody }));
2875
- }
2876
- return {
2877
- GET: async ({ request }) => handle(request),
2878
- POST: async ({ request }) => handle(request),
2879
- DELETE: async ({ request }) => handle(request),
2880
- OPTIONS: async ({ request }) => handle(request)
2881
- };
2882
- }
2883
- async function runDocsMcpStdio(options) {
2884
- const server = await createDocsMcpServer({
2885
- ...options,
2886
- requestContext: { transport: "stdio" }
2887
- });
2888
- const transport = new StdioServerTransport();
2889
- await server.connect(transport);
2890
- }
2891
- async function isDocsMcpOriginAllowed(request, allowedOrigins) {
2892
- const origin = request.headers.get("origin");
2893
- if (!origin) return true;
2894
- if (typeof allowedOrigins === "function") return allowedOrigins({
2895
- origin,
2896
- request
2897
- });
2898
- const normalizedOrigin = normalizeHttpOrigin(origin);
2899
- if (allowedOrigins === "same-origin") return normalizedOrigin === new URL(request.url).origin;
2900
- return allowedOrigins.some((allowedOrigin) => normalizeHttpOrigin(allowedOrigin) === normalizedOrigin);
2901
- }
2902
- function normalizeHttpOrigin(value) {
2903
- try {
2904
- return new URL(value).origin;
2905
- } catch {
2906
- return value.trim();
2907
- }
2908
- }
2909
- function isDocsMcpAuthPrincipal(value) {
2910
- if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
2911
- const principal = value;
2912
- if (typeof principal.id !== "string" || principal.id.trim().length === 0) return false;
2913
- if (principal.scopes !== void 0 && (!Array.isArray(principal.scopes) || principal.scopes.some((scope) => typeof scope !== "string"))) return false;
2914
- return principal.claims === void 0 || typeof principal.claims === "object" && principal.claims !== null && !Array.isArray(principal.claims);
2915
- }
2916
- async function prepareDocsMcpHttpRequest(request, maxBodyBytes) {
2917
- if (request.method.toUpperCase() !== "POST") return {
2918
- status: "ok",
2919
- request,
2920
- bodyParseFailed: false
2921
- };
2922
- if (isContentLengthOverLimit(request, maxBodyBytes)) return { status: "too-large" };
2923
- const body = request.body;
2924
- if (!body) return {
2925
- status: "ok",
2926
- request,
2927
- bodyParseFailed: true
2928
- };
2929
- const reader = body.getReader();
2930
- const chunks = [];
2931
- let byteLength = 0;
2932
- while (true) {
2933
- const { done, value } = await reader.read();
2934
- if (done) break;
2935
- byteLength += value.byteLength;
2936
- if (byteLength > maxBodyBytes) {
2937
- reader.cancel();
2938
- return { status: "too-large" };
2939
- }
2940
- chunks.push(value);
2941
- }
2942
- const bodyBytes = new Uint8Array(byteLength);
2943
- let offset = 0;
2944
- for (const chunk of chunks) {
2945
- bodyBytes.set(chunk, offset);
2946
- offset += chunk.byteLength;
2947
- }
2948
- const boundedRequest = new Request(request.url, {
2949
- method: request.method,
2950
- headers: new Headers(request.headers),
2951
- body: bodyBytes,
2952
- redirect: request.redirect,
2953
- signal: request.signal
2954
- });
2955
- try {
2956
- return {
2957
- status: "ok",
2958
- request: boundedRequest,
2959
- parsedBody: JSON.parse(new TextDecoder().decode(bodyBytes)),
2960
- bodyParseFailed: false
2961
- };
2962
- } catch {
2963
- return {
2964
- status: "ok",
2965
- request: boundedRequest,
2966
- bodyParseFailed: true
2967
- };
2968
- }
2969
- }
2970
- function isContentLengthOverLimit(request, maxBodyBytes) {
2971
- const rawContentLength = request.headers.get("content-length");
2972
- if (rawContentLength === null) return false;
2973
- const contentLength = Number(rawContentLength);
2974
- return Number.isFinite(contentLength) && contentLength > maxBodyBytes;
2975
- }
2976
- const DOCS_MCP_CORS_METHODS = [
2977
- "GET",
2978
- "POST",
2979
- "DELETE",
2980
- "OPTIONS"
2981
- ];
2982
- const DOCS_MCP_CORS_REQUEST_METHODS = new Set([
2983
- "GET",
2984
- "POST",
2985
- "DELETE"
2986
- ]);
2987
- function createDocsMcpOptionsResponse(request, cors) {
2988
- const allow = DOCS_MCP_CORS_METHODS.join(", ");
2989
- if (!request.headers.get("origin") || !cors.enabled) return new Response(null, {
2990
- status: 204,
2991
- headers: { Allow: allow }
2992
- });
2993
- const requestedMethod = request.headers.get("access-control-request-method")?.trim().toUpperCase();
2994
- if (requestedMethod && !DOCS_MCP_CORS_REQUEST_METHODS.has(requestedMethod)) return applyDocsMcpPreflightCorsHeaders(createMcpHttpSecurityErrorResponse(405, "CORS request method is not allowed"), request, cors);
2995
- const allowedHeaders = new Set(cors.allowedHeaders.map((header) => header.toLowerCase()));
2996
- const rejectedHeader = parseCorsRequestedHeaders(request.headers.get("access-control-request-headers")).find((header) => !allowedHeaders.has(header.toLowerCase()));
2997
- if (rejectedHeader) return applyDocsMcpPreflightCorsHeaders(createMcpHttpSecurityErrorResponse(403, `CORS request header is not allowed: ${rejectedHeader}`), request, cors);
2998
- return applyDocsMcpPreflightCorsHeaders(new Response(null, {
2999
- status: 204,
3000
- headers: { Allow: allow }
3001
- }), request, cors);
3002
- }
3003
- function parseCorsRequestedHeaders(value) {
3004
- if (!value) return [];
3005
- return value.split(",").map((header) => header.trim()).filter(Boolean);
3006
- }
3007
- function applyDocsMcpPreflightCorsHeaders(response, request, cors) {
3008
- const headers = buildDocsMcpCorsHeaders(response.headers, request, cors);
3009
- headers.set("Access-Control-Allow-Methods", DOCS_MCP_CORS_METHODS.join(", "));
3010
- headers.set("Access-Control-Allow-Headers", cors.allowedHeaders.join(", "));
3011
- headers.set("Access-Control-Max-Age", String(cors.maxAgeSeconds));
3012
- appendVaryHeader(headers, "Access-Control-Request-Method");
3013
- appendVaryHeader(headers, "Access-Control-Request-Headers");
3014
- return cloneResponseWithHeaders(response, headers);
3015
- }
3016
- function applyDocsMcpCorsHeaders(response, request, cors) {
3017
- if (!request.headers.has("origin") || !cors.enabled) return response;
3018
- const headers = buildDocsMcpCorsHeaders(response.headers, request, cors);
3019
- if (!headers.has("Access-Control-Allow-Origin")) return response;
3020
- if (cors.exposedHeaders.length > 0) headers.set("Access-Control-Expose-Headers", cors.exposedHeaders.join(", "));
3021
- return cloneResponseWithHeaders(response, headers);
3022
- }
3023
- function buildDocsMcpCorsHeaders(source, request, cors) {
3024
- const headers = new Headers(source);
3025
- headers.delete("Access-Control-Allow-Origin");
3026
- headers.delete("Access-Control-Allow-Credentials");
3027
- const origin = serializeCorsOrigin(request.headers.get("origin"));
3028
- if (!origin) return headers;
3029
- headers.set("Access-Control-Allow-Origin", origin);
3030
- if (cors.allowCredentials) headers.set("Access-Control-Allow-Credentials", "true");
3031
- else headers.delete("Access-Control-Allow-Credentials");
3032
- appendVaryHeader(headers, "Origin");
3033
- return headers;
3034
- }
3035
- function serializeCorsOrigin(value) {
3036
- if (!value) return null;
3037
- if (value.trim() === "null") return "null";
3038
- try {
3039
- const origin = new URL(value).origin;
3040
- return origin === "null" ? null : origin;
3041
- } catch {
3042
- return null;
3043
- }
3044
- }
3045
- function appendVaryHeader(headers, value) {
3046
- const values = (headers.get("Vary") ?? "").split(",").map((entry) => entry.trim()).filter(Boolean);
3047
- if (!values.some((entry) => entry.toLowerCase() === value.toLowerCase())) values.push(value);
3048
- headers.set("Vary", values.join(", "));
3049
- }
3050
- function cloneResponseWithHeaders(response, headers) {
3051
- return new Response(response.body, {
3052
- status: response.status,
3053
- statusText: response.statusText,
3054
- headers
3055
- });
3056
- }
3057
- function createMcpRequestTooLargeResponse(maxBodyBytes) {
3058
- const response = createJsonRpcErrorResponse({
3059
- status: 413,
3060
- code: -32e3,
3061
- message: `Request body exceeds the ${maxBodyBytes} byte limit`,
3062
- data: {
3063
- reason: "request_too_large",
3064
- maxBodyBytes
3065
- }
3066
- });
3067
- response.headers.set("Cache-Control", "no-store");
3068
- return response;
3069
- }
3070
- function createMcpHttpSecurityErrorResponse(status, error) {
3071
- return new Response(JSON.stringify({ error }), {
3072
- status,
3073
- headers: {
3074
- "Content-Type": "application/json",
3075
- "Cache-Control": "no-store"
3076
- }
3077
- });
3078
- }
3079
- function createJsonErrorResponse(status, error) {
3080
- return new Response(JSON.stringify({ error }), {
3081
- status,
3082
- headers: { "Content-Type": "application/json" }
3083
- });
3084
- }
3085
- async function parseJsonBody(request) {
3086
- try {
3087
- return await request.clone().json();
3088
- } catch {
3089
- return;
3090
- }
3091
- }
3092
- function readJsonRpcId(value) {
3093
- if (!value || typeof value !== "object" || Array.isArray(value)) return null;
3094
- const id = value.id;
3095
- return typeof id === "string" || typeof id === "number" ? id : null;
3096
- }
3097
- function createJsonRpcErrorResponse({ status, code, message, id = null, data }) {
3098
- return new Response(JSON.stringify({
3099
- jsonrpc: "2.0",
3100
- id,
3101
- error: data ? {
3102
- code,
3103
- message,
3104
- data
3105
- } : {
3106
- code,
3107
- message
3108
- }
3109
- }), {
3110
- status,
3111
- headers: { "Content-Type": "application/json" }
3112
- });
3113
- }
3114
- function normalizePathSegment(value) {
3115
- return value.replace(/^\/+|\/+$/g, "");
3116
- }
3117
- function titleize(value) {
3118
- return value.replace(/-/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
3119
- }
3120
- function stripMarkdownForMcp(content) {
3121
- return content.replace(/^(import|export)\s.*$/gm, "").replace(/<[^>]+\/>/g, "").replace(/<\/?[A-Z][^>]*>/g, "").replace(/<\/?[a-z][^>]*>/g, "").replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/^#{1,6}\s+/gm, "").replace(/(\*{1,3}|_{1,3})(.*?)\1/g, "$2").replace(/```[\s\S]*?```/g, "").replace(/`([^`]+)`/g, "$1").replace(/^>\s+/gm, "").replace(/^[-*_]{3,}\s*$/gm, "").replace(/\n{3,}/g, "\n\n").trim();
3122
- }
3123
- function resolveFilesystemDocsPageSource(dir) {
3124
- return [
3125
- "page.mdx",
3126
- "page.md",
3127
- "page.svx"
3128
- ].map((fileName) => path.join(dir, fileName)).find((candidate) => fs.existsSync(candidate));
3129
- }
3130
- function hasVisibleDescendantFilesystemDocsPage(dir) {
3131
- let entries;
3132
- try {
3133
- entries = fs.readdirSync(dir);
3134
- } catch {
3135
- return false;
3136
- }
3137
- for (const name of entries.sort()) {
3138
- const full = path.join(dir, name);
3139
- try {
3140
- if (!fs.statSync(full).isDirectory()) continue;
3141
- } catch {
3142
- continue;
3143
- }
3144
- const pageSource = resolveFilesystemDocsPageSource(full);
3145
- if (pageSource) try {
3146
- const data = matter(fs.readFileSync(pageSource, "utf-8")).data;
3147
- const hiddenFolderIndex = resolvePageSidebarFolderIndexBehavior(data.sidebar) === "hidden";
3148
- if (data.hidden !== true && !hiddenFolderIndex) return true;
3149
- } catch {
3150
- return true;
3151
- }
3152
- if (hasVisibleDescendantFilesystemDocsPage(full)) return true;
3153
- }
3154
- return false;
3155
- }
3156
- function scanFilesystemDocsPages(contentDirAbs, entry, rootDir) {
3157
- const pages = [];
3158
- function scan(dir, slugParts) {
3159
- if (!fs.existsSync(dir)) return;
3160
- const entries = fs.readdirSync(dir).sort();
3161
- for (const name of entries) {
3162
- const full = path.join(dir, name);
3163
- const stat = fs.statSync(full);
3164
- if (stat.isDirectory()) {
3165
- scan(full, [...slugParts, name]);
3166
- continue;
3167
- }
3168
- if (name === "agent.md") continue;
3169
- if (!name.endsWith(".md") && !name.endsWith(".mdx") && !name.endsWith(".svx")) continue;
3170
- const { data, content } = matter(fs.readFileSync(full, "utf-8"));
3171
- const baseName = name.replace(/\.(md|mdx|svx)$/, "");
3172
- const isIndex = baseName === "index" || baseName === "page" || baseName === "+page";
3173
- if (isIndex && resolvePageSidebarFolderIndexBehavior(data.sidebar) === "hidden" && hasVisibleDescendantFilesystemDocsPage(dir)) continue;
3174
- const humanRawContent = resolveDocsAudienceMdxContent(content, "human");
3175
- const pageAgentRawContent = resolveDocsAudienceMdxContent(content, "agent");
3176
- const pageAgentContent = pageAgentRawContent !== humanRawContent ? stripMarkdownForMcp(pageAgentRawContent) : void 0;
3177
- const slug = isIndex ? slugParts.join("/") : [...slugParts, baseName].join("/");
3178
- const url = slug ? `/${entry}/${slug}` : `/${entry}`;
3179
- const agentDoc = isIndex ? readFilesystemAgentDoc(dir) : void 0;
3180
- const title = data.title ?? (isIndex ? slugParts.length > 0 ? titleize(slugParts[slugParts.length - 1]) : "Documentation" : titleize(baseName));
3181
- pages.push({
3182
- slug,
3183
- url,
3184
- title,
3185
- description: data.description,
3186
- relatedInput: data.related,
3187
- agent: normalizePageAgentFrontmatter(data.agent),
3188
- icon: data.icon,
3189
- sourcePath: path.relative(rootDir, full).replace(/\\/g, "/"),
3190
- lastModified: stat.mtime.toISOString(),
3191
- locale: typeof data.locale === "string" ? data.locale : void 0,
3192
- framework: typeof data.framework === "string" ? data.framework : void 0,
3193
- version: typeof data.version === "string" ? data.version : void 0,
3194
- tags: Array.isArray(data.tags) ? data.tags.filter((tag) => typeof tag === "string") : void 0,
3195
- content: stripMarkdownForMcp(humanRawContent),
3196
- rawContent: humanRawContent,
3197
- agentFallbackContent: pageAgentContent,
3198
- agentFallbackRawContent: pageAgentRawContent !== humanRawContent ? pageAgentRawContent : void 0,
3199
- order: typeof data.order === "number" ? data.order : Number.POSITIVE_INFINITY,
3200
- ...agentDoc
3201
- });
3202
- }
3203
- }
3204
- scan(contentDirAbs, []);
3205
- return resolveRelatedForMcpPages(pages);
3206
- }
3207
- function readFilesystemAgentDoc(dir) {
3208
- const agentPath = path.join(dir, "agent.md");
3209
- if (!fs.existsSync(agentPath)) return void 0;
3210
- const { content } = matter(stripGeneratedAgentProvenance(fs.readFileSync(agentPath, "utf-8")));
3211
- const agentContent = resolveDocsAudienceMdxContent(content, "agent");
3212
- return {
3213
- agentContent: stripMarkdownForMcp(agentContent),
3214
- agentRawContent: agentContent
3215
- };
3216
- }
3217
- function resolveRelatedForMcpPages(pages) {
3218
- return pages.map(({ relatedInput, ...page }) => {
3219
- const related = normalizeDocsRelated(relatedInput);
3220
- return related.length > 0 ? {
3221
- ...page,
3222
- related
3223
- } : page;
3224
- });
3225
- }
3226
- function buildNavigationTreeFromPages(pages, siteTitle, ordering) {
3227
- const bySlug = new Map(pages.map((page) => [page.slug, page]));
3228
- const rootPage = bySlug.get("");
3229
- function childOrderFor(parentSlug) {
3230
- if (!Array.isArray(ordering)) return void 0;
3231
- if (!parentSlug) return ordering;
3232
- let items = ordering;
3233
- for (const segment of parentSlug.split("/")) {
3234
- items = (items?.find((item) => item.slug === segment))?.children;
3235
- if (!items) return void 0;
3236
- }
3237
- return items;
3238
- }
3239
- function sortChildSlugs(childSlugs, parentSlug) {
3240
- const explicitOrder = childOrderFor(parentSlug);
3241
- if (explicitOrder) {
3242
- const explicit = new Set(explicitOrder.map((item) => item.slug));
3243
- const ordered = [];
3244
- for (const item of explicitOrder) {
3245
- const childSlug = parentSlug ? `${parentSlug}/${item.slug}` : item.slug;
3246
- if (childSlugs.includes(childSlug)) ordered.push(childSlug);
3247
- }
3248
- for (const childSlug of childSlugs) {
3249
- const segment = childSlug.split("/").pop() ?? childSlug;
3250
- if (!explicit.has(segment)) ordered.push(childSlug);
3251
- }
3252
- return ordered;
3253
- }
3254
- if (ordering === "numeric") return [...childSlugs].sort((left, right) => {
3255
- const leftPage = bySlug.get(left);
3256
- const rightPage = bySlug.get(right);
3257
- const leftOrder = leftPage?.order ?? Number.POSITIVE_INFINITY;
3258
- const rightOrder = rightPage?.order ?? Number.POSITIVE_INFINITY;
3259
- if (leftOrder !== rightOrder) return leftOrder - rightOrder;
3260
- return left.localeCompare(right);
3261
- });
3262
- return [...childSlugs].sort((left, right) => left.localeCompare(right));
3263
- }
3264
- function buildLevel(parentSlug) {
3265
- const prefix = parentSlug ? `${parentSlug}/` : "";
3266
- const childSet = /* @__PURE__ */ new Set();
3267
- for (const page of pages) {
3268
- if (!page.slug.startsWith(prefix) || page.slug === parentSlug) continue;
3269
- const remainder = page.slug.slice(prefix.length);
3270
- if (!remainder) continue;
3271
- const [firstSegment] = remainder.split("/");
3272
- childSet.add(parentSlug ? `${parentSlug}/${firstSegment}` : firstSegment);
3273
- }
3274
- const childSlugs = sortChildSlugs([...childSet], parentSlug);
3275
- const nodes = [];
3276
- for (const childSlug of childSlugs) {
3277
- const page = bySlug.get(childSlug);
3278
- const hasChildren = pages.some((candidate) => candidate.slug.startsWith(`${childSlug}/`));
3279
- const segment = childSlug.split("/").pop() ?? childSlug;
3280
- const name = page?.title ?? titleize(segment);
3281
- const icon = page?.icon;
3282
- const description = page?.description;
3283
- if (hasChildren) {
3284
- nodes.push({
3285
- type: "folder",
3286
- name,
3287
- icon,
3288
- index: page ? {
3289
- type: "page",
3290
- name: page.title,
3291
- url: page.url,
3292
- icon: page.icon,
3293
- description: page.description
3294
- } : void 0,
3295
- children: buildLevel(childSlug)
3296
- });
3297
- continue;
3298
- }
3299
- if (!page) continue;
3300
- nodes.push({
3301
- type: "page",
3302
- name,
3303
- url: page.url,
3304
- icon,
3305
- description
3306
- });
3307
- }
3308
- return nodes;
3309
- }
3310
- const children = [];
3311
- if (rootPage) children.push({
3312
- type: "page",
3313
- name: rootPage.title,
3314
- url: rootPage.url,
3315
- icon: rootPage.icon,
3316
- description: rootPage.description
3317
- });
3318
- children.push(...buildLevel(""));
3319
- return {
3320
- name: siteTitle,
3321
- children
3322
- };
3323
- }
3324
- function dedupePages(pages) {
3325
- const seen = /* @__PURE__ */ new Map();
3326
- for (const page of pages) seen.set(page.url, page);
3327
- return [...seen.values()];
3328
- }
3329
- function toSearchSourcePages(pages) {
3330
- return pages.map((page) => ({
3331
- title: page.title,
3332
- url: page.url,
3333
- content: page.content,
3334
- rawContent: page.rawContent,
3335
- sourcePath: page.sourcePath,
3336
- lastModified: page.lastModified,
3337
- locale: page.locale,
3338
- framework: page.framework,
3339
- version: page.version,
3340
- tags: page.tags,
3341
- agentContent: page.agentContent,
3342
- agentRawContent: page.agentRawContent,
3343
- agentFallbackContent: page.agentFallbackContent,
3344
- agentFallbackRawContent: page.agentFallbackRawContent,
3345
- description: page.description,
3346
- related: page.related,
3347
- agent: page.agent
3348
- }));
3349
- }
3350
- function getDocsConfigSchema(filters = {}) {
3351
- const option = filters.option?.trim();
3352
- const query = filters.query?.trim();
3353
- let options = DOCS_CONFIG_SCHEMA_OPTIONS.map(cloneConfigSchemaOption);
3354
- if (option) options = selectConfigSchemaOptions(option);
3355
- if (query) options = filterConfigSchemaOptionsByQuery(options, query);
3356
- return {
3357
- schemaVersion: 1,
3358
- configFile: "docs.config.ts",
3359
- description: "Configuration schema for @farming-labs/docs defineDocs(). Use option for an exact top-level or nested path, or query for keyword filtering.",
3360
- filters: option || query ? {
3361
- ...option ? { option } : {},
3362
- ...query ? { query } : {}
3363
- } : void 0,
3364
- resultCount: countConfigSchemaOptions(options),
3365
- options,
3366
- examples: DOCS_CONFIG_SCHEMA_EXAMPLES.map((example) => ({ ...example }))
3367
- };
3368
- }
3369
- function cloneConfigSchemaOption(option) {
3370
- return {
3371
- ...option,
3372
- values: option.values ? [...option.values] : void 0,
3373
- children: option.children?.map(cloneConfigSchemaOption)
3374
- };
3375
- }
3376
- function selectConfigSchemaOptions(optionPath) {
3377
- const needle = normalizeConfigSchemaToken(optionPath);
3378
- return flattenConfigSchemaOptions(DOCS_CONFIG_SCHEMA_OPTIONS).filter((option) => {
3379
- return normalizeConfigSchemaToken(option.path) === needle;
3380
- }).map(cloneConfigSchemaOption);
3381
- }
3382
- function filterConfigSchemaOptionsByQuery(options, query) {
3383
- return options.flatMap((option) => {
3384
- if (configSchemaOptionMatchesQuery(option, query)) return [cloneConfigSchemaOption(option)];
3385
- const children = option.children ? filterConfigSchemaOptionsByQuery(option.children, query) : [];
3386
- if (children.length === 0) return [];
3387
- return [{
3388
- ...cloneConfigSchemaOption(option),
3389
- children
3390
- }];
3391
- });
3392
- }
3393
- function configSchemaOptionMatchesQuery(option, query) {
3394
- const searchText = [
3395
- option.path,
3396
- option.name,
3397
- option.type,
3398
- option.default,
3399
- option.description,
3400
- option.docs,
3401
- option.values?.join(" ")
3402
- ].filter((value) => value !== void 0 && value !== null).join(" ");
3403
- const lowerSearchText = searchText.toLowerCase();
3404
- const lowerQuery = query.toLowerCase();
3405
- return lowerSearchText.includes(lowerQuery) || normalizeConfigSchemaToken(searchText).includes(normalizeConfigSchemaToken(query));
3406
- }
3407
- function flattenConfigSchemaOptions(options) {
3408
- return options.flatMap((option) => [option, ...option.children ? flattenConfigSchemaOptions(option.children) : []]);
3409
- }
3410
- function countConfigSchemaOptions(options) {
3411
- return flattenConfigSchemaOptions(options).length;
3412
- }
3413
- function normalizeConfigSchemaToken(value) {
3414
- return value.trim().toLowerCase().replace(/^docs\.config\.?/, "").replace(/[`'"]/g, "").replace(/[_\-\s]+/g, "");
3415
- }
3416
- function isSelfMcpSearchEndpoint(search, route) {
3417
- if (!search || search === true || typeof search !== "object" || search.provider !== "mcp") return false;
3418
- const endpoint = search.endpoint.trim();
3419
- if (!endpoint.startsWith("/")) return false;
3420
- return normalizeDocsMcpRoute(endpoint) === normalizeDocsMcpRoute(route);
3421
- }
3422
- function resolveMcpToolSearchConfig(search, route) {
3423
- if (!isSelfMcpSearchEndpoint(search, route)) return search;
3424
- const config = search;
3425
- return {
3426
- provider: "simple",
3427
- enabled: config.enabled,
3428
- maxResults: config.maxResults,
3429
- chunking: config.chunking
3430
- };
3431
- }
3432
- function toAgentContractSummary(value) {
3433
- const agent = normalizePageAgentFrontmatter(value);
3434
- const hasContract = hasStructuredPageAgentContract(agent);
3435
- return {
3436
- hasContract,
3437
- ...hasContract && agent?.task ? { task: agent.task } : {},
3438
- ...hasContract && agent?.outcome ? { outcome: agent.outcome } : {},
3439
- ...hasContract && agent?.appliesTo ? { appliesTo: agent.appliesTo } : {}
3440
- };
3441
- }
3442
- function toPageSummaries(pages) {
3443
- return pages.map((page) => ({
3444
- slug: page.slug,
3445
- url: page.url,
3446
- title: page.title,
3447
- description: page.description,
3448
- icon: page.icon,
3449
- agent: toAgentContractSummary(page.agent)
3450
- }));
3451
- }
3452
- function toDocsListPageSummary(page) {
3453
- return {
3454
- slug: page.slug,
3455
- url: page.url,
3456
- title: page.title,
3457
- description: page.description,
3458
- agent: toAgentContractSummary(page.agent),
3459
- icon: page.icon,
3460
- sourcePath: page.sourcePath,
3461
- lastModified: page.lastModified
3462
- };
3463
- }
3464
- function listDocsTasks(pages, filters) {
3465
- const query = filters.query?.toLowerCase();
3466
- const applicabilityFilters = [
3467
- ["framework", filters.framework],
3468
- ["version", filters.version],
3469
- ["package", filters.package]
3470
- ];
3471
- return pages.flatMap((page) => {
3472
- const agent = normalizePageAgentFrontmatter(page.agent);
3473
- if (!agent || !hasStructuredPageAgentContract(agent)) return [];
3474
- for (const [field, expected] of applicabilityFilters) {
3475
- if (!expected) continue;
3476
- const actualValue = agent.appliesTo?.[field];
3477
- if (!(typeof actualValue === "string" ? [actualValue] : actualValue ?? []).some((value) => value.toLowerCase() === expected.toLowerCase())) return [];
3478
- }
3479
- if (query) {
3480
- if (![
3481
- page.slug,
3482
- page.url,
3483
- page.title,
3484
- page.description,
3485
- JSON.stringify(agent)
3486
- ].filter(Boolean).join("\n").toLowerCase().includes(query)) return [];
3487
- }
3488
- return [{
3489
- slug: page.slug,
3490
- url: page.url,
3491
- title: page.title,
3492
- ...page.description ? { description: page.description } : {},
3493
- ...agent.task ? { task: agent.task } : {},
3494
- ...agent.outcome ? { outcome: agent.outcome } : {},
3495
- ...agent.appliesTo ? { appliesTo: agent.appliesTo } : {}
3496
- }];
3497
- });
3498
- }
3499
- function listDocsBySection(pages, filters) {
3500
- const allPages = pages.map(toDocsListPageSummary);
3501
- const tree = buildDocsSectionTree(pages);
3502
- const requestedSection = filters.section?.trim();
3503
- if (!requestedSection) return {
3504
- resultCount: allPages.length,
3505
- sectionCount: countDocsSections(tree.sections),
3506
- pages: allPages,
3507
- rootPages: tree.rootPages,
3508
- sections: tree.sections
3509
- };
3510
- const section = findDocsSection(tree.sections, requestedSection, filters.entry);
3511
- if (section) {
3512
- const sections = [cloneDocsSection(section)];
3513
- const matchedPages = flattenDocsSectionPages(sections[0]);
3514
- return {
3515
- section: requestedSection,
3516
- resultCount: matchedPages.length,
3517
- sectionCount: countDocsSections(sections),
3518
- pages: matchedPages,
3519
- rootPages: [],
3520
- sections
3521
- };
3522
- }
3523
- const page = allPages.find((candidate) => docsListPageMatches(candidate, requestedSection, filters.entry));
3524
- if (page) return {
3525
- section: requestedSection,
3526
- resultCount: 1,
3527
- sectionCount: 0,
3528
- pages: [page],
3529
- rootPages: [page],
3530
- sections: []
3531
- };
3532
- return {
3533
- section: requestedSection,
3534
- resultCount: 0,
3535
- sectionCount: 0,
3536
- pages: [],
3537
- rootPages: [],
3538
- sections: []
3539
- };
3540
- }
3541
- function buildDocsSectionTree(pages) {
3542
- const sectionSlugs = /* @__PURE__ */ new Set();
3543
- for (const page of pages) {
3544
- const parts = page.slug.split("/").filter(Boolean);
3545
- for (let index = 1; index < parts.length; index += 1) sectionSlugs.add(parts.slice(0, index).join("/"));
3546
- }
3547
- const rootPages = [];
3548
- const sections = [];
3549
- const sectionBySlug = /* @__PURE__ */ new Map();
3550
- function getOrCreateSection(slug) {
3551
- const existing = sectionBySlug.get(slug);
3552
- if (existing) return existing;
3553
- const parts = slug.split("/").filter(Boolean);
3554
- const section = {
3555
- slug,
3556
- title: titleize(parts.at(-1) ?? slug),
3557
- pageCount: 0,
3558
- pages: [],
3559
- sections: []
3560
- };
3561
- sectionBySlug.set(slug, section);
3562
- if (parts.length <= 1) sections.push(section);
3563
- else getOrCreateSection(parts.slice(0, -1).join("/")).sections.push(section);
3564
- return section;
3565
- }
3566
- for (const page of pages) {
3567
- const summary = toDocsListPageSummary(page);
3568
- const parts = page.slug.split("/").filter(Boolean);
3569
- if (parts.length === 0) {
3570
- rootPages.push(summary);
3571
- continue;
3572
- }
3573
- const isSectionIndex = sectionSlugs.has(page.slug);
3574
- if (parts.length === 1 && !isSectionIndex) {
3575
- rootPages.push(summary);
3576
- continue;
3577
- }
3578
- if (isSectionIndex) {
3579
- const section = getOrCreateSection(page.slug);
3580
- hydrateDocsSection(section, summary);
3581
- pushUniqueDocsPage(section.pages, summary, "start");
3582
- continue;
3583
- }
3584
- pushUniqueDocsPage(getOrCreateSection(parts.slice(0, -1).join("/")).pages, summary, "end");
3585
- }
3586
- updateDocsSectionPageCounts(sections);
3587
- return {
3588
- rootPages,
3589
- sections
3590
- };
3591
- }
3592
- function hydrateDocsSection(section, page) {
3593
- section.title = page.title;
3594
- section.url = page.url;
3595
- section.description = page.description;
3596
- section.icon = page.icon;
3597
- }
3598
- function pushUniqueDocsPage(pages, page, position) {
3599
- if (pages.some((candidate) => candidate.url === page.url)) return;
3600
- if (position === "start") {
3601
- pages.unshift(page);
3602
- return;
3603
- }
3604
- pages.push(page);
3605
- }
3606
- function updateDocsSectionPageCounts(sections) {
3607
- let total = 0;
3608
- for (const section of sections) {
3609
- section.pageCount = section.pages.length + updateDocsSectionPageCounts(section.sections);
3610
- total += section.pageCount;
3611
- }
3612
- return total;
3613
- }
3614
- function findDocsSection(sections, section, entry) {
3615
- for (const candidate of sections) {
3616
- if (docsListSectionMatches(candidate, section, entry)) return candidate;
3617
- const child = findDocsSection(candidate.sections, section, entry);
3618
- if (child) return child;
3619
- }
3620
- }
3621
- function docsListSectionMatches(section, value, entry) {
3622
- return docsListCandidates(section, entry).includes(normalizeDocsListMatchValue(value));
3623
- }
3624
- function docsListPageMatches(page, value, entry) {
3625
- return docsListCandidates(page, entry).includes(normalizeDocsListMatchValue(value));
3626
- }
3627
- function docsListCandidates(value, entry) {
3628
- return [
3629
- value.slug,
3630
- value.url,
3631
- value.title,
3632
- value.url ? stripDocsEntryFromPath(value.url, entry) : void 0,
3633
- stripDocsEntryFromPath(value.slug, entry)
3634
- ].filter((candidate) => Boolean(candidate)).map(normalizeDocsListMatchValue);
3635
- }
3636
- function stripDocsEntryFromPath(value, entry) {
3637
- const normalized = normalizePathSegment(value.replace(/\.md$/i, ""));
3638
- const normalizedEntry = normalizePathSegment(entry ?? "");
3639
- if (!normalizedEntry) return normalized;
3640
- if (normalized === normalizedEntry) return "";
3641
- if (normalized.startsWith(`${normalizedEntry}/`)) return normalized.slice(normalizedEntry.length + 1);
3642
- return normalized;
3643
- }
3644
- function normalizeDocsListMatchValue(value) {
3645
- return normalizePathSegment(value.replace(/^https?:\/\/[^/]+/i, "").replace(/\.md$/i, "")).trim().toLowerCase().replace(/\s+/g, "-").replace(/_+/g, "-").replace(/\/+/g, "/").replace(/^\/+|\/+$/g, "");
3646
- }
3647
- function cloneDocsSection(section) {
3648
- return {
3649
- ...section,
3650
- pages: section.pages.map((page) => ({ ...page })),
3651
- sections: section.sections.map(cloneDocsSection)
3652
- };
3653
- }
3654
- function flattenDocsSectionPages(section) {
3655
- const seen = /* @__PURE__ */ new Set();
3656
- const pages = [];
3657
- function add(page) {
3658
- if (seen.has(page.url)) return;
3659
- seen.add(page.url);
3660
- pages.push({ ...page });
3661
- }
3662
- function visit(current) {
3663
- current.pages.forEach(add);
3664
- current.sections.forEach(visit);
3665
- }
3666
- visit(section);
3667
- return pages;
3668
- }
3669
- function countDocsSections(sections) {
3670
- return sections.reduce((total, section) => total + 1 + countDocsSections(section.sections), 0);
3671
- }
3672
- function extractDocsMcpCodeExamples(page) {
3673
- const source = page.agentRawContent ?? page.agentFallbackRawContent ?? page.agentContent ?? page.agentFallbackContent ?? page.rawContent ?? page.content;
3674
- if (!source) return [];
3675
- const examples = [];
3676
- const lines = source.split("\n");
3677
- let index = 0;
3678
- let openFence = null;
3679
- for (const line of lines) {
3680
- const trimmed = line.trim();
3681
- if (!openFence) {
3682
- const openMatch = trimmed.match(/^(`{3,}|~{3,})(.*)$/);
3683
- if (!openMatch) continue;
3684
- openFence = {
3685
- marker: openMatch[1],
3686
- info: openMatch[2]?.trim() ?? "",
3687
- code: []
3688
- };
3689
- continue;
3690
- }
3691
- if (isClosingFence(trimmed, openFence.marker)) {
3692
- const parsed = parseCodeFenceInfo(openFence.info);
3693
- const meta = parsed.meta;
3694
- const title = readStringMeta(meta, "title");
3695
- const framework = readStringMeta(meta, "framework");
3696
- const packageManager = readStringMeta(meta, "packageManager");
3697
- const runnable = readBooleanMeta(meta, "runnable") ?? false;
3698
- index += 1;
3699
- examples.push({
3700
- id: `${page.url}#code-${index}`,
3701
- page: {
3702
- slug: page.slug,
3703
- url: page.url,
3704
- title: page.title,
3705
- description: page.description,
3706
- sourcePath: page.sourcePath,
3707
- lastModified: page.lastModified
3708
- },
3709
- language: parsed.language,
3710
- title,
3711
- framework,
3712
- packageManager,
3713
- runnable,
3714
- meta,
3715
- code: openFence.code.join("\n")
3716
- });
3717
- openFence = null;
3718
- continue;
3719
- }
3720
- openFence.code.push(line);
3721
- }
3722
- return examples;
3723
- }
3724
- function filterDocsCodeExamples(examples, filters) {
3725
- const query = filters.query?.toLowerCase();
3726
- const framework = filters.framework?.toLowerCase();
3727
- const packageManager = filters.packageManager?.toLowerCase();
3728
- const language = filters.language?.toLowerCase();
3729
- return examples.filter((example) => {
3730
- if (framework && example.framework?.toLowerCase() !== framework) return false;
3731
- if (packageManager && example.packageManager?.toLowerCase() !== packageManager) return false;
3732
- if (language && example.language?.toLowerCase() !== language) return false;
3733
- if (filters.runnable !== void 0 && example.runnable !== filters.runnable) return false;
3734
- if (!query) return true;
3735
- return getCodeExampleSearchText(example).toLowerCase().includes(query);
3736
- }).slice(0, filters.limit);
3737
- }
3738
- function isClosingFence(trimmedLine, marker) {
3739
- if (!trimmedLine.startsWith(marker)) return false;
3740
- return trimmedLine.slice(marker.length).trim().length === 0;
3741
- }
3742
- function parseCodeFenceInfo(info) {
3743
- const trimmed = info.trim();
3744
- if (!trimmed) return { meta: {} };
3745
- const firstToken = trimmed.match(/^(\S+)/)?.[1] ?? "";
3746
- const language = firstToken && !firstToken.includes("=") ? firstToken : void 0;
3747
- const attributeSource = language ? trimmed.slice(firstToken.length).trim() : trimmed;
3748
- const meta = {};
3749
- const attributePattern = /([A-Za-z_:][\w:.-]*)(?:=(?:"([^"]*)"|'([^']*)'|([^\s"']+)))?/g;
3750
- let match;
3751
- while (match = attributePattern.exec(attributeSource)) {
3752
- const key = match[1];
3753
- meta[key] = match[2] ?? match[3] ?? match[4] ?? true;
3754
- }
3755
- return {
3756
- language,
3757
- meta
3758
- };
3759
- }
3760
- function readStringMeta(meta, key) {
3761
- const value = meta[key];
3762
- return typeof value === "string" && value.trim().length > 0 ? value : void 0;
3763
- }
3764
- function readBooleanMeta(meta, key) {
3765
- const value = meta[key];
3766
- if (typeof value === "boolean") return value;
3767
- if (typeof value !== "string") return void 0;
3768
- const normalized = value.trim().toLowerCase();
3769
- if (!normalized || normalized === "true" || normalized === "1" || normalized === "yes") return true;
3770
- if (normalized === "false" || normalized === "0" || normalized === "no") return false;
3771
- return true;
3772
- }
3773
- function getCodeExampleSearchText(example) {
3774
- return [
3775
- example.id,
3776
- example.page.slug,
3777
- example.page.url,
3778
- example.page.title,
3779
- example.page.description,
3780
- example.page.sourcePath,
3781
- example.language,
3782
- example.title,
3783
- example.framework,
3784
- example.packageManager,
3785
- ...Object.entries(example.meta).map(([key, value]) => `${key} ${String(value)}`),
3786
- example.code
3787
- ].filter((value) => typeof value === "string").join("\n");
3788
- }
3789
- function limitDocsMcpText(value, maxChars) {
3790
- if (maxChars === void 0 || value.length <= maxChars) return {
3791
- text: value,
3792
- truncated: false
3793
- };
3794
- if (maxChars <= 1) return {
3795
- text: value.slice(0, maxChars),
3796
- truncated: true
3797
- };
3798
- const suffix = "…";
3799
- const available = Math.max(0, maxChars - 1);
3800
- let selected = value.slice(0, available);
3801
- const paragraphBreak = selected.lastIndexOf("\n\n");
3802
- const lineBreak = selected.lastIndexOf("\n");
3803
- const preferredBreak = paragraphBreak >= available * .6 ? paragraphBreak : lineBreak;
3804
- if (preferredBreak >= available * .6) selected = selected.slice(0, preferredBreak);
3805
- return {
3806
- text: `${selected.trimEnd()}${suffix}`.slice(0, maxChars),
3807
- truncated: true
3808
- };
3809
- }
3810
- function docsMcpUtf8Bytes(value) {
3811
- return UTF8_ENCODER.encode(value).byteLength;
3812
- }
3813
- function limitDocsMcpUtf8Bytes(value, maxUtf8Bytes) {
3814
- if (docsMcpUtf8Bytes(value) <= maxUtf8Bytes) return {
3815
- text: value,
3816
- truncated: false
3817
- };
3818
- if (maxUtf8Bytes <= 0) return {
3819
- text: "",
3820
- truncated: true
3821
- };
3822
- const suffix = "…";
3823
- const suffixBytes = docsMcpUtf8Bytes(suffix);
3824
- const availableBytes = Math.max(0, maxUtf8Bytes - suffixBytes);
3825
- let selected = "";
3826
- let selectedBytes = 0;
3827
- for (const character of value) {
3828
- const characterBytes = docsMcpUtf8Bytes(character);
3829
- if (selectedBytes + characterBytes > availableBytes) break;
3830
- selected += character;
3831
- selectedBytes += characterBytes;
3832
- }
3833
- const paragraphBreak = selected.lastIndexOf("\n\n");
3834
- const lineBreak = selected.lastIndexOf("\n");
3835
- const preferredBreak = paragraphBreak >= selected.length * .6 ? paragraphBreak : lineBreak;
3836
- if (preferredBreak >= selected.length * .6) selected = selected.slice(0, preferredBreak);
3837
- if (suffixBytes > maxUtf8Bytes) return {
3838
- text: ".".repeat(maxUtf8Bytes),
3839
- truncated: true
3840
- };
3841
- return {
3842
- text: `${selected.trimEnd()}${suffix}`,
3843
- truncated: true
3844
- };
3845
- }
3846
- function renderBoundedDocsMcpSectionError(options) {
3847
- const maxChars = options.maxChars ?? Number.POSITIVE_INFINITY;
3848
- const shorten = (value, max) => limitDocsMcpText(value, max).text;
3849
- const availableSections = [];
3850
- const base = {
3851
- error: "section_not_found",
3852
- message: `No section matched "${shorten(options.requestedSection, 80)}" in "${shorten(options.pageUrl, 80)}".`,
3853
- availableSections,
3854
- truncated: false
3855
- };
3856
- const serialize = () => JSON.stringify(base, null, 2);
3857
- for (const section of options.availableSections) {
3858
- availableSections.push({
3859
- title: shorten(section.title, 80),
3860
- anchor: shorten(section.anchor, 80)
3861
- });
3862
- if (serialize().length > maxChars) {
3863
- availableSections.pop();
3864
- base.truncated = true;
3865
- break;
3866
- }
3867
- }
3868
- if (availableSections.length < options.availableSections.length) base.truncated = true;
3869
- if (serialize().length <= maxChars) return serialize();
3870
- const minimalText = JSON.stringify({
3871
- error: "section_not_found",
3872
- message: "The requested section was not found.",
3873
- availableSections: [],
3874
- truncated: true
3875
- });
3876
- if (minimalText.length <= maxChars) return minimalText;
3877
- return JSON.stringify({ error: "section_not_found" });
3878
- }
3879
- function toStructuredDocsMcpPage(page) {
3880
- return {
3881
- slug: page.slug,
3882
- url: page.url,
3883
- title: page.title,
3884
- description: page.description,
3885
- related: page.related,
3886
- icon: page.icon,
3887
- sourcePath: page.sourcePath,
3888
- lastModified: page.lastModified,
3889
- locale: page.locale,
3890
- framework: page.framework,
3891
- version: page.version,
3892
- tags: page.tags
3893
- };
3894
- }
3895
- function getDocsMcpSourceMarkdown(page) {
3896
- return page.agentRawContent ?? page.agentFallbackRawContent ?? page.agentContent ?? page.agentFallbackContent ?? page.rawContent ?? page.content;
3897
- }
3898
- function resolveDocsMcpScopeField(pageValue, contractValue, filter, overlaps, matchesFilter) {
3899
- const pageValues = normalizeAgentScopeValues(pageValue);
3900
- const contractValues = normalizeAgentScopeValues(contractValue);
3901
- const conflict = pageValues.length > 0 && contractValues.length > 0 && !pageValues.some((topLevel) => contractValues.some((contract) => overlaps(topLevel, contract)));
3902
- const pageMatches = !filter || pageValues.length === 0 || pageValues.some((candidate) => matchesFilter(filter, candidate));
3903
- const contractMatches = !filter || contractValues.length === 0 || contractValues.some((candidate) => matchesFilter(filter, candidate));
3904
- const matchedContract = filter ? contractValues.find((candidate) => matchesFilter(filter, candidate)) : void 0;
3905
- return {
3906
- conflict,
3907
- matches: !conflict && pageMatches && contractMatches,
3908
- value: pageValues[0] ?? matchedContract ?? (contractValues.length === 1 ? contractValues[0] : void 0)
3909
- };
3910
- }
3911
- function resolveDocsMcpEffectiveScope(page, filters) {
3912
- const framework = resolveDocsMcpScopeField(page.framework, page.agent?.appliesTo?.framework, filters.framework, (left, right) => normalizeAgentFramework(left) === normalizeAgentFramework(right), (filter, candidate) => normalizeAgentFramework(filter) === normalizeAgentFramework(candidate));
3913
- const version = resolveDocsMcpScopeField(page.version, page.agent?.appliesTo?.version, filters.version, agentVersionConstraintsOverlap, agentVersionConstraintMatches);
3914
- const locale = page.locale?.trim() || void 0;
3915
- const localeMatches = !filters.locale || !locale || normalizeAgentLocale(locale) === normalizeAgentLocale(filters.locale);
3916
- return {
3917
- framework: framework.value,
3918
- version: version.value,
3919
- locale,
3920
- conflict: framework.conflict || version.conflict,
3921
- matches: framework.matches && version.matches && localeMatches
3922
- };
3923
- }
3924
- function getDocsMcpResultPageUrl(value) {
3925
- return value.split("#", 1)[0] ?? value;
3926
- }
3927
- function getDocsMcpResultAnchor(value) {
3928
- const anchor = value.split("#", 2)[1];
3929
- if (!anchor) return void 0;
3930
- try {
3931
- return decodeURIComponent(anchor);
3932
- } catch {
3933
- return anchor;
3934
- }
3935
- }
3936
- async function buildDocsMcpContext(options) {
3937
- const scopedPageEntries = options.pages.flatMap((page) => {
3938
- const scope = resolveDocsMcpEffectiveScope(page, {
3939
- framework: options.framework,
3940
- version: options.version,
3941
- locale: options.locale
3942
- });
3943
- return scope.matches && !scope.conflict ? [{
3944
- page,
3945
- scope
3946
- }] : [];
3947
- });
3948
- const scopedPages = scopedPageEntries.map(({ page }) => page);
3949
- const scopeByPage = new Map(scopedPageEntries.map(({ page, scope }) => [page, scope]));
3950
- const maxResults = typeof options.maxResults === "number" && Number.isFinite(options.maxResults) ? Math.max(1, Math.min(50, Math.floor(options.maxResults))) : 50;
3951
- const orderedResults = [...await performDocsSearch({
3952
- pages: toSearchSourcePages(scopedPages),
3953
- query: options.query,
3954
- search: {
3955
- enabled: true,
3956
- provider: "simple",
3957
- maxResults: 50,
3958
- chunking: { strategy: "section" }
3959
- },
3960
- audience: "agent",
3961
- locale: options.locale,
3962
- siteTitle: options.siteTitle,
3963
- limit: 50
3964
- })].sort((left, right) => {
3965
- const scoreDelta = (right.score ?? 0) - (left.score ?? 0);
3966
- if (scoreDelta !== 0) return scoreDelta;
3967
- if (left.url !== right.url) return left.url < right.url ? -1 : 1;
3968
- if ((left.section ?? "") !== (right.section ?? "")) return (left.section ?? "") < (right.section ?? "") ? -1 : 1;
3969
- if (left.id === right.id) return 0;
3970
- return left.id < right.id ? -1 : 1;
3971
- });
3972
- const sectionPageUrls = new Set(orderedResults.filter((result) => result.section).map((result) => normalizeUrlPath(getDocsMcpResultPageUrl(result.url))));
3973
- const seen = /* @__PURE__ */ new Set();
3974
- const resolvedCandidates = orderedResults.filter((result) => {
3975
- const pageUrl = normalizeUrlPath(getDocsMcpResultPageUrl(result.url));
3976
- if (!result.section && sectionPageUrls.has(pageUrl)) return false;
3977
- const key = `${pageUrl}#${getDocsMcpResultAnchor(result.url) ?? result.section ?? ""}`;
3978
- if (seen.has(key)) return false;
3979
- seen.add(key);
3980
- return true;
3981
- }).flatMap((result) => {
3982
- const page = findDocsPage(scopedPages, getDocsMcpResultPageUrl(result.url), options.entry);
3983
- if (!page) return [];
3984
- const scope = scopeByPage.get(page);
3985
- if (!scope) return [];
3986
- const document = upsertPageAgentContractMarkdown(getDocsMcpSourceMarkdown(page), page.agent);
3987
- const resultAnchor = getDocsMcpResultAnchor(result.url);
3988
- const selectedSection = resultAnchor ? findDocsMarkdownSection(document, resultAnchor) : result.section ? findDocsMarkdownSection(document, result.section) : void 0;
3989
- if ((resultAnchor || result.section) && !selectedSection) return [];
3990
- const rawContent = (selectedSection?.content ?? document).trim();
3991
- if (!rawContent) return [];
3992
- return [{
3993
- result,
3994
- page,
3995
- scope,
3996
- selectedSection,
3997
- rawContent
3998
- }];
3999
- }).slice(0, maxResults);
4000
- const maxUtf8Bytes = options.tokenBudget;
4001
- const separator = "\n\n---\n\n";
4002
- const separatorUtf8Bytes = docsMcpUtf8Bytes(separator);
4003
- const blocks = [];
4004
- const sources = [];
4005
- let usedUtf8Bytes = 0;
4006
- for (const { result, page, scope, selectedSection, rawContent } of resolvedCandidates) {
4007
- const anchor = selectedSection?.anchor;
4008
- const sourceUrl = anchor ? `${page.url}#${anchor}` : page.url;
4009
- const headerLines = [`## ${page.title}`, `Source: ${sourceUrl}`];
4010
- if (selectedSection?.title) headerLines.push(`Section: ${selectedSection.title}`);
4011
- if (scope.framework) headerLines.push(`Framework: ${scope.framework}`);
4012
- if (scope.version) headerLines.push(`Version: ${scope.version}`);
4013
- if (scope.locale) headerLines.push(`Locale: ${scope.locale}`);
4014
- const header = headerLines.join("\n");
4015
- const separatorBytes = blocks.length === 0 ? 0 : separatorUtf8Bytes;
4016
- const headerBytes = docsMcpUtf8Bytes(`${header}\n\n`);
4017
- const availableForContent = maxUtf8Bytes - usedUtf8Bytes - separatorBytes - headerBytes;
4018
- if (availableForContent <= 0) break;
4019
- const limited = limitDocsMcpUtf8Bytes(rawContent, availableForContent);
4020
- if (!limited.text) break;
4021
- const block = `${header}\n\n${limited.text}`;
4022
- const blockUtf8Bytes = docsMcpUtf8Bytes(block);
4023
- blocks.push(block);
4024
- usedUtf8Bytes += separatorBytes + blockUtf8Bytes;
4025
- sources.push({
4026
- id: result.id,
4027
- title: page.title,
4028
- pageUrl: page.url,
4029
- url: sourceUrl,
4030
- section: selectedSection?.title,
4031
- anchor,
4032
- sourcePath: page.sourcePath,
4033
- lastModified: page.lastModified,
4034
- locale: scope.locale,
4035
- framework: scope.framework,
4036
- version: scope.version,
4037
- tags: page.tags ? [...page.tags] : void 0,
4038
- score: result.score,
4039
- content: limited.text,
4040
- chars: limited.text.length,
4041
- utf8Bytes: docsMcpUtf8Bytes(limited.text),
4042
- truncated: limited.truncated
4043
- });
4044
- if (limited.truncated) break;
4045
- }
4046
- const context = blocks.join(separator);
4047
- usedUtf8Bytes = docsMcpUtf8Bytes(context);
4048
- const remainingUtf8Bytes = Math.max(0, maxUtf8Bytes - usedUtf8Bytes);
4049
- const truncated = sources.some((source) => source.truncated) || sources.length < resolvedCandidates.length;
4050
- return {
4051
- query: options.query,
4052
- filters: {
4053
- framework: options.framework,
4054
- version: options.version,
4055
- locale: options.locale
4056
- },
4057
- budget: {
4058
- requestedTokens: options.tokenBudget,
4059
- strategy: "utf8-bytes",
4060
- maxUtf8Bytes,
4061
- usedUtf8Bytes,
4062
- conservativeTokenUpperBound: usedUtf8Bytes,
4063
- remainingUtf8Bytes,
4064
- truncated
4065
- },
4066
- resultCount: sources.length,
4067
- candidateCount: resolvedCandidates.length,
4068
- context,
4069
- sources
4070
- };
4071
- }
4072
- function findDocsPage(pages, requestedPath, entry) {
4073
- const normalizedRequest = normalizeRequestedPath(requestedPath, entry);
4074
- for (const page of pages) if (normalizeUrlPath(page.url) === normalizedRequest) return page;
4075
- const normalizedSlug = normalizePathSegment(requestedPath.replace(/^\//, ""));
4076
- for (const page of pages) if (normalizePathSegment(page.slug) === normalizedSlug) return page;
4077
- return null;
4078
- }
4079
- function normalizeRequestedPath(requestedPath, entry) {
4080
- const trimmed = requestedPath.trim();
4081
- if (!trimmed) return "/";
4082
- if (/^https?:\/\//i.test(trimmed)) try {
4083
- return normalizeUrlPath(new URL(trimmed).pathname);
4084
- } catch {
4085
- return "/";
4086
- }
4087
- const normalized = normalizeUrlPath(trimmed.startsWith("/") ? trimmed : `/${trimmed}`);
4088
- if (!entry) return normalized;
4089
- const normalizedEntry = `/${normalizePathSegment(entry)}`;
4090
- if (normalized === normalizedEntry || normalized.startsWith(`${normalizedEntry}/`)) return normalized;
4091
- const slug = normalizePathSegment(trimmed);
4092
- return slug ? normalizeUrlPath(`${normalizedEntry}/${slug}`) : normalizedEntry;
4093
- }
4094
- function normalizeUrlPath(value) {
4095
- const normalized = value.replace(/\/+/g, "/");
4096
- if (normalized === "/") return normalized;
4097
- return normalized.replace(/\/+$/, "");
4098
- }
4099
- function renderPageDocument(page) {
4100
- const explicitAgentContent = page.agentRawContent ?? page.agentContent;
4101
- if (explicitAgentContent !== void 0) return upsertPageAgentContractMarkdown(explicitAgentContent, page.agent);
4102
- const relatedLines = renderDocsRelatedMarkdownLines(page.related);
4103
- const lines = [`# ${page.title}`, `URL: ${page.url}`];
4104
- if (page.description) lines.push(`Description: ${page.description}`);
4105
- lines.push(...relatedLines);
4106
- lines.push("", upsertPageAgentContractMarkdown(page.agentFallbackRawContent ?? page.agentFallbackContent ?? page.rawContent ?? page.content, page.agent));
4107
- return lines.join("\n");
4108
- }
4109
- function renderNavigationTree(tree) {
4110
- const lines = [`# ${tree.name}`, ""];
4111
- function visit(nodes, depth) {
4112
- const prefix = " ".repeat(depth);
4113
- for (const node of nodes) {
4114
- if (node.type === "page") {
4115
- lines.push(`${prefix}- ${node.name} (${node.url})`);
4116
- continue;
4117
- }
4118
- lines.push(`${prefix}- ${node.name}`);
4119
- if (node.index) lines.push(`${prefix} - Overview (${node.index.url})`);
4120
- visit(node.children, depth + 1);
4121
- }
4122
- }
4123
- visit(tree.children, 0);
4124
- return lines.join("\n");
4125
- }
4126
- function slugToKey(slug) {
4127
- const normalized = normalizePathSegment(slug);
4128
- return normalized.length > 0 ? normalized.replace(/\//g, "-") : "index";
4129
- }
4130
- function toPageResourceUri(url) {
4131
- return `docs://${normalizePathSegment(url.replace(/^\//, "")) || "docs"}`;
4132
- }
4133
-
4134
- //#endregion
4135
- 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 };