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