@farming-labs/docs 0.2.60 → 0.2.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{agent-DlxriaTs.mjs → agent-DFHlw_JC.mjs} +3 -3
- package/dist/{agent-BFqyqEnC.mjs → agent-Fl0pjVNF.mjs} +136 -331
- package/dist/{agent-evals-kJs2Y9xR.mjs → agent-evals-f4_OL10T.mjs} +3 -3
- package/dist/{agent-export-BgUaiW8f.mjs → agent-export-D0zQhasD.mjs} +87 -46
- package/dist/agent-skills-bundle.d.mts +13 -0
- package/dist/agent-skills-bundle.mjs +12 -0
- package/dist/agent-skills-server-CIeBszxp.mjs +263 -0
- package/dist/agent-skills-server-CKq3_jMj.d.mts +12 -0
- package/dist/agent-skills-vite.d.mts +31 -0
- package/dist/agent-skills-vite.mjs +70 -0
- package/dist/{agents-Djh-HXih.mjs → agents-ibnXrWyp.mjs} +9 -7
- package/dist/cli/index.mjs +29 -29
- package/dist/client/react.d.mts +1 -1
- package/dist/{cloud-ask-ai-B2WnG4fF.d.mts → cloud-ask-ai-D7ZgF47y.d.mts} +1 -1
- package/dist/{cloud-BH_sHX64.mjs → cloud-pdNC-tyj.mjs} +3 -3
- package/dist/{codeblocks-Bq67u32v.mjs → codeblocks-CFuurVIH.mjs} +2 -2
- package/dist/{config-DASewQ0x.mjs → config-Wcdj-D0a.mjs} +7 -1
- package/dist/{dev-DgY5xGl9.mjs → dev-Cmy6DtdF.mjs} +2 -2
- package/dist/docs-cloud-server.d.mts +2 -2
- package/dist/docs-cloud-server.mjs +1 -1
- package/dist/{doctor-CO1VMcF_.mjs → doctor-CXostbsI.mjs} +151 -21
- package/dist/{downgrade-BZs86NVr.mjs → downgrade-w7e6Se0L.mjs} +2 -2
- package/dist/{golden-evaluations-BN9u2wxw.mjs → golden-evaluations-CBZ_JZjf.mjs} +19 -4
- package/dist/index.d.mts +52 -10
- package/dist/index.mjs +8 -7
- package/dist/{init-Bd_k06bR.mjs → init-CQY0Woe3.mjs} +36 -5
- package/dist/{mcp-B_yXL5G5.mjs → mcp-DyPcoLwm.mjs} +33 -14
- package/dist/mcp.d.mts +13 -2
- package/dist/mcp.mjs +699 -408
- package/dist/{metadata-BDuewuzq.mjs → metadata-Dv1ah0Aj.mjs} +1 -1
- package/dist/{package-version-qik_4J6C.mjs → package-version-n5AFur8a.mjs} +1 -1
- package/dist/{reading-time-BkEft6SD.mjs → reading-time-C-SAhQT9.mjs} +10 -9
- package/dist/{review-NC-sOdXn.mjs → review-D2UBrxFq.mjs} +18 -10
- package/dist/{robots-DskPvGPw.mjs → robots-5Yqz9mz7.mjs} +5 -4
- package/dist/{robots-ltltiLJF.mjs → robots-C08kDLsz.mjs} +6 -2
- package/dist/{search-D57JXQLj.mjs → search-9OnMGMvt.mjs} +1 -1
- package/dist/{search-C1JitPwi.d.mts → search-C6heDO8h.d.mts} +3 -126
- package/dist/{search-o4Ud6OXv.mjs → search-CNsRpz90.mjs} +8 -6
- package/dist/server.d.mts +6 -5
- package/dist/server.mjs +9 -7
- package/dist/{sitemap-Cq-Yj_iA.mjs → sitemap-BpYnSsfJ.mjs} +9 -7
- package/dist/{sitemap-server-C1ibVKOy.mjs → sitemap-server-D_0Kzanj.mjs} +1 -1
- package/dist/standards-discovery-C4HUqMd2.d.mts +227 -0
- package/dist/standards-discovery-C54V_aJH.mjs +510 -0
- package/dist/{templates-DNw15P-x.mjs → templates-Bq_P7ctv.mjs} +101 -9
- package/dist/{types-XHABMh_f.d.mts → types-EhFhYGfr.d.mts} +58 -1
- package/dist/{upgrade-BCJTCW3O.mjs → upgrade-oz-GChgt.mjs} +2 -2
- package/package.json +11 -1
- /package/dist/{agent-scope-CCaIY1aK.mjs → agent-scope-C_U--OZ7.mjs} +0 -0
- /package/dist/{cloud-ask-ai-hnJfj8-X.mjs → cloud-ask-ai-sbpjOR2K.mjs} +0 -0
- /package/dist/{code-blocks-qe0T8-xe.mjs → code-blocks-DnNVNK2M.mjs} +0 -0
- /package/dist/{errors-CVqZ3kOO.mjs → errors-DbOhkE1h.mjs} +0 -0
- /package/dist/{i18n-CAlj1ADU.mjs → i18n-CCaFUnAN.mjs} +0 -0
- /package/dist/{utils-6UCLxv4B.mjs → utils-DpiIioYb.mjs} +0 -0
package/dist/mcp.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { a as emitDocsAnalyticsEvent, i as emitDocsAgentTraceEvent, n as createDocsAgentTraceContext, r as createDocsAgentTraceId } from "./analytics-Bx44lg6d.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
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";
|
|
5
6
|
import matter from "gray-matter";
|
|
6
7
|
import fs from "node:fs";
|
|
7
8
|
import path from "node:path";
|
|
@@ -12,7 +13,6 @@ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
|
12
13
|
import * as z from "zod/v4";
|
|
13
14
|
|
|
14
15
|
//#region src/mcp.ts
|
|
15
|
-
const DEFAULT_MCP_ROUTE = "/api/docs/mcp";
|
|
16
16
|
const DEFAULT_MCP_VERSION = "0.0.0";
|
|
17
17
|
const DEFAULT_MCP_NAME = "@farming-labs/docs";
|
|
18
18
|
const DEFAULT_MCP_CONTEXT_TOKEN_BUDGET = 4e3;
|
|
@@ -222,405 +222,490 @@ const DOCS_CONFIG_SCHEMA_OPTIONS_TEMPLATE = [
|
|
|
222
222
|
type: "DocsAgentConfig",
|
|
223
223
|
description: "Agent compaction defaults and offline-by-default usefulness evaluations.",
|
|
224
224
|
docs: "/docs/getting-started/agent-ready-docs",
|
|
225
|
-
children: [
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
{
|
|
232
|
-
path: "agent.
|
|
233
|
-
name: "
|
|
234
|
-
type: "string",
|
|
235
|
-
description: "
|
|
236
|
-
},
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
name: "apiKeyEnv",
|
|
240
|
-
type: "string",
|
|
241
|
-
description: "Environment variable containing the compaction provider API key."
|
|
242
|
-
},
|
|
243
|
-
{
|
|
244
|
-
path: "agent.compact.baseUrl",
|
|
245
|
-
name: "baseUrl",
|
|
246
|
-
type: "string",
|
|
247
|
-
description: "Compaction provider base URL."
|
|
248
|
-
},
|
|
249
|
-
{
|
|
250
|
-
path: "agent.compact.model",
|
|
251
|
-
name: "model",
|
|
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",
|
|
252
239
|
type: "string",
|
|
253
|
-
description: "
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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",
|
|
361
434
|
type: "string",
|
|
362
|
-
description: "
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
]
|
|
373
|
-
},
|
|
374
|
-
{
|
|
375
|
-
path: "agent.evaluations.tasks",
|
|
376
|
-
name: "tasks",
|
|
377
|
-
type: "DocsAgentGoldenTask[]",
|
|
378
|
-
description: "Golden task fixtures evaluated by docs doctor and docs review; the default MCP context surface runs offline.",
|
|
379
|
-
children: [
|
|
380
|
-
{
|
|
381
|
-
path: "agent.evaluations.tasks[]",
|
|
382
|
-
name: "task",
|
|
383
|
-
type: "DocsAgentGoldenTask",
|
|
384
|
-
description: "One golden task entry."
|
|
385
|
-
},
|
|
386
|
-
{
|
|
387
|
-
path: "agent.evaluations.tasks[].id",
|
|
388
|
-
name: "id",
|
|
389
|
-
type: "string",
|
|
390
|
-
description: "Stable task identifier shown in diagnostics."
|
|
391
|
-
},
|
|
392
|
-
{
|
|
393
|
-
path: "agent.evaluations.tasks[].query",
|
|
394
|
-
name: "query",
|
|
395
|
-
type: "string",
|
|
396
|
-
description: "User-shaped retrieval query."
|
|
397
|
-
},
|
|
398
|
-
{
|
|
399
|
-
path: "agent.evaluations.tasks[].tokenBudget",
|
|
400
|
-
name: "tokenBudget",
|
|
401
|
-
type: "number",
|
|
402
|
-
description: "Per-task UTF-8 context-byte ceiling override."
|
|
403
|
-
},
|
|
404
|
-
{
|
|
405
|
-
path: "agent.evaluations.tasks[].topK",
|
|
406
|
-
name: "topK",
|
|
407
|
-
type: "number",
|
|
408
|
-
description: "Per-task ranked retrieval depth override."
|
|
409
|
-
},
|
|
410
|
-
{
|
|
411
|
-
path: "agent.evaluations.tasks[].surface",
|
|
412
|
-
name: "surface",
|
|
413
|
-
type: "\"mcp-context\" | \"configured-search\" | \"ask-ai-context\"",
|
|
414
|
-
description: "Per-task evaluation surface override."
|
|
415
|
-
},
|
|
416
|
-
{
|
|
417
|
-
path: "agent.evaluations.tasks[].filters",
|
|
418
|
-
name: "filters",
|
|
419
|
-
type: "DocsAgentGoldenTaskFilters",
|
|
420
|
-
description: "Framework, version, and locale retrieval scope.",
|
|
421
|
-
children: [
|
|
422
|
-
{
|
|
423
|
-
path: "agent.evaluations.tasks[].filters.framework",
|
|
424
|
-
name: "framework",
|
|
425
|
-
type: "string",
|
|
426
|
-
description: "Required framework, such as nextjs or astro."
|
|
427
|
-
},
|
|
428
|
-
{
|
|
429
|
-
path: "agent.evaluations.tasks[].filters.version",
|
|
430
|
-
name: "version",
|
|
431
|
-
type: "string",
|
|
432
|
-
description: "Exact version requested by the task."
|
|
433
|
-
},
|
|
434
|
-
{
|
|
435
|
-
path: "agent.evaluations.tasks[].filters.locale",
|
|
436
|
-
name: "locale",
|
|
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",
|
|
437
445
|
type: "string",
|
|
438
|
-
description: "
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
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
|
+
]
|
|
624
709
|
},
|
|
625
710
|
{
|
|
626
711
|
path: "review",
|
|
@@ -966,6 +1051,45 @@ const DOCS_CONFIG_SCHEMA_OPTIONS_TEMPLATE = [
|
|
|
966
1051
|
default: "public (callback omitted)",
|
|
967
1052
|
description: "Opt-in HTTP authentication callback. Return a principal to continue, null for 401, or a Response to control the rejection."
|
|
968
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
|
+
},
|
|
969
1093
|
{
|
|
970
1094
|
path: "mcp.security.maxBodyBytes",
|
|
971
1095
|
name: "maxBodyBytes",
|
|
@@ -1199,13 +1323,18 @@ export default defineDocs({
|
|
|
1199
1323
|
});`
|
|
1200
1324
|
},
|
|
1201
1325
|
{
|
|
1202
|
-
title: "Opt-in MCP authentication",
|
|
1326
|
+
title: "Opt-in OAuth MCP authentication",
|
|
1203
1327
|
code: `export default defineDocs({
|
|
1204
1328
|
mcp: {
|
|
1205
1329
|
security: {
|
|
1330
|
+
protectedResource: {
|
|
1331
|
+
authorizationServers: ["https://auth.example.com"],
|
|
1332
|
+
scopesSupported: ["docs:read"],
|
|
1333
|
+
requiredScopes: ["docs:read"],
|
|
1334
|
+
},
|
|
1206
1335
|
async authenticate({ request }) {
|
|
1207
1336
|
const user = await authenticateRequest(request);
|
|
1208
|
-
return user ? { id: user.id, scopes:
|
|
1337
|
+
return user ? { id: user.id, scopes: user.scopes } : null;
|
|
1209
1338
|
},
|
|
1210
1339
|
},
|
|
1211
1340
|
},
|
|
@@ -1519,9 +1648,7 @@ const contextOutputSchema = z.object({
|
|
|
1519
1648
|
sources: z.array(contextSourceOutputSchema)
|
|
1520
1649
|
});
|
|
1521
1650
|
function normalizeDocsMcpRoute(route) {
|
|
1522
|
-
|
|
1523
|
-
const normalized = `/${route}`.replace(/\/+/g, "/");
|
|
1524
|
-
return normalized !== "/" ? normalized.replace(/\/+$/, "") : DEFAULT_MCP_ROUTE;
|
|
1651
|
+
return normalizeDocsMcpEndpointPath(route);
|
|
1525
1652
|
}
|
|
1526
1653
|
function resolveDocsMcpConfig(mcp, defaults = {}) {
|
|
1527
1654
|
if (mcp === false) return {
|
|
@@ -1570,10 +1697,52 @@ function resolveDocsMcpSecurityConfig(security) {
|
|
|
1570
1697
|
return {
|
|
1571
1698
|
allowedOrigins: security?.allowedOrigins ?? "same-origin",
|
|
1572
1699
|
authenticate: security?.authenticate,
|
|
1700
|
+
protectedResource: resolveDocsMcpProtectedResourceConfig(security?.protectedResource),
|
|
1573
1701
|
maxBodyBytes,
|
|
1574
1702
|
cors: resolveDocsMcpCorsConfig(security?.cors)
|
|
1575
1703
|
};
|
|
1576
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
|
+
}
|
|
1577
1746
|
function resolveDocsMcpCorsConfig(cors) {
|
|
1578
1747
|
const config = cors && typeof cors === "object" ? cors : {};
|
|
1579
1748
|
const configuredMaxAge = config.maxAgeSeconds;
|
|
@@ -1661,6 +1830,9 @@ async function createDocsMcpServer(options) {
|
|
|
1661
1830
|
function getSourceNavigation(locale) {
|
|
1662
1831
|
return options.source.getNavigation(locale, options.requestContext);
|
|
1663
1832
|
}
|
|
1833
|
+
function getSourceSkills() {
|
|
1834
|
+
return options.source.getSkills?.(options.requestContext) ?? [];
|
|
1835
|
+
}
|
|
1664
1836
|
function trackMcpTool(tool, values) {
|
|
1665
1837
|
emitDocsTelemetryMcpToolEvent(telemetryConfig, {
|
|
1666
1838
|
framework: telemetryFramework,
|
|
@@ -1672,6 +1844,7 @@ async function createDocsMcpServer(options) {
|
|
|
1672
1844
|
}
|
|
1673
1845
|
const defaultPages = dedupePages(await getSourcePages());
|
|
1674
1846
|
const defaultTree = await getSourceNavigation();
|
|
1847
|
+
const defaultSkills = await getSourceSkills();
|
|
1675
1848
|
server.registerResource("docs-navigation", "docs://navigation", {
|
|
1676
1849
|
title: "Docs Navigation",
|
|
1677
1850
|
description: "Structured navigation tree for the documentation site.",
|
|
@@ -1693,6 +1866,23 @@ async function createDocsMcpServer(options) {
|
|
|
1693
1866
|
text: renderPageDocument(page)
|
|
1694
1867
|
}] }));
|
|
1695
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
|
+
}
|
|
1696
1886
|
if (resolved.tools.listPages) server.registerTool("list_pages", {
|
|
1697
1887
|
title: "List docs pages",
|
|
1698
1888
|
description: "List the known documentation pages with titles, slugs, and URLs.",
|
|
@@ -2802,12 +2992,26 @@ function createDocsMcpHttpHandler(options) {
|
|
|
2802
2992
|
};
|
|
2803
2993
|
}
|
|
2804
2994
|
async function handle(request) {
|
|
2805
|
-
const
|
|
2995
|
+
const originalUrl = new URL(request.url);
|
|
2806
2996
|
const method = request.method.toUpperCase();
|
|
2807
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");
|
|
2808
3011
|
const prepared = await prepareDocsMcpHttpRequest(request, security.maxBodyBytes);
|
|
2809
3012
|
if (prepared.status === "too-large") return createMcpRequestTooLargeResponse(security.maxBodyBytes);
|
|
2810
3013
|
request = prepared.request;
|
|
3014
|
+
const url = new URL(request.url);
|
|
2811
3015
|
let originAllowed;
|
|
2812
3016
|
try {
|
|
2813
3017
|
originAllowed = await isDocsMcpOriginAllowed(request.clone(), security.allowedOrigins);
|
|
@@ -2823,14 +3027,16 @@ function createDocsMcpHttpHandler(options) {
|
|
|
2823
3027
|
try {
|
|
2824
3028
|
authentication = await security.authenticate({
|
|
2825
3029
|
request: request.clone(),
|
|
2826
|
-
pathname:
|
|
3030
|
+
pathname: resourceLocation.resourceUrl.pathname,
|
|
3031
|
+
resource: serializeMcpResourceIdentifier(resourceLocation.resourceUrl)
|
|
2827
3032
|
});
|
|
2828
3033
|
} catch {
|
|
2829
3034
|
return withCors(createMcpHttpSecurityErrorResponse(500, "MCP authentication failed"));
|
|
2830
3035
|
}
|
|
2831
3036
|
if (authentication instanceof Response) return withCors(authentication);
|
|
2832
|
-
if (authentication === null || authentication === void 0) return withCors(
|
|
3037
|
+
if (authentication === null || authentication === void 0) return withCors(createMcpUnauthorizedResponse(request, resourceLocation, security.protectedResource));
|
|
2833
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));
|
|
2834
3040
|
auth = authentication;
|
|
2835
3041
|
}
|
|
2836
3042
|
const sessionId = request.headers.get("mcp-session-id") ?? request.headers.get("Mcp-Session-Id");
|
|
@@ -3054,6 +3260,91 @@ function cloneResponseWithHeaders(response, headers) {
|
|
|
3054
3260
|
headers
|
|
3055
3261
|
});
|
|
3056
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
|
+
}
|
|
3057
3348
|
function createMcpRequestTooLargeResponse(maxBodyBytes) {
|
|
3058
3349
|
const response = createJsonRpcErrorResponse({
|
|
3059
3350
|
status: 413,
|