@farming-labs/docs 0.2.103 → 0.2.105

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/dist/{agent-BLHMoVId.mjs → agent-CaCWThBY.mjs} +4 -4
  2. package/dist/{agent-CFqECAG0.mjs → agent-DCTGj6J7.mjs} +212 -19
  3. package/dist/{agent-evals-GpcaCbRb.mjs → agent-evals-D-sPJfXz.mjs} +5 -5
  4. package/dist/{agent-export-oiWxB5aX.mjs → agent-export-GcTZPLfB.mjs} +30 -17
  5. package/dist/agent-feedback-evaluations-DQCTFegz.mjs +240 -0
  6. package/dist/agent-feedback-loop-vaC-M1sx.mjs +196 -0
  7. package/dist/agent-maintenance-CuZ7iAKq.mjs +289 -0
  8. package/dist/agent-skills-bundle.d.mts +1 -1
  9. package/dist/{agent-skills-progressive-disclosure-Da9LKEKv.mjs → agent-skills-progressive-disclosure-DL-q4JZo.mjs} +1 -1
  10. package/dist/{agent-skills-server-CICMh0zv.d.mts → agent-skills-server-D34054x5.d.mts} +2 -2
  11. package/dist/{agent-skills-server-DFqQyO_V.mjs → agent-skills-server-Ddbf15pY.mjs} +1 -1
  12. package/dist/agent-skills-vite.d.mts +3 -3
  13. package/dist/agent-skills-vite.mjs +2 -2
  14. package/dist/{agents-CtqIUW5I.mjs → agents-e1SYT64z.mjs} +11 -10
  15. package/dist/{analytics-Bx44lg6d.mjs → analytics-CqfcIBdg.mjs} +1 -1
  16. package/dist/{analytics-C75BnTEW.d.mts → analytics-aeXwEvQX.d.mts} +1 -1
  17. package/dist/authoring-Do87QzBd.mjs +41 -0
  18. package/dist/authoring-mcp-Be7quE5R.mjs +380 -0
  19. package/dist/browser.d.mts +3 -3
  20. package/dist/browser.mjs +2 -2
  21. package/dist/cli/index.mjs +103 -35
  22. package/dist/client/react.d.mts +1 -1
  23. package/dist/client/react.mjs +1 -1
  24. package/dist/{cloud-FNfcbfli.mjs → cloud-Bvof6mET.mjs} +3 -3
  25. package/dist/{cloud-ask-ai-BmbZVAT5.d.mts → cloud-ask-ai-BqfiQaNZ.d.mts} +1 -1
  26. package/dist/{cloud-ask-ai-CkCYjf2N.mjs → cloud-ask-ai-CK2qlNns.mjs} +1 -1
  27. package/dist/{code-blocks-Bel6vDqI.mjs → code-blocks-D90CCJFs.mjs} +1 -1
  28. package/dist/{codeblocks-jbWuvtJQ.mjs → codeblocks-C911CdyJ.mjs} +2 -2
  29. package/dist/{agent-lWTq6pgr.d.mts → content-change-hydration-CoYRs3pC.d.mts} +707 -675
  30. package/dist/{dev-BvbYmTLz.mjs → dev-Bx0JNglW.mjs} +3 -3
  31. package/dist/docs-cloud-server.d.mts +2 -2
  32. package/dist/docs-cloud-server.mjs +2 -2
  33. package/dist/{doctor-Bmm0uY98.mjs → doctor-CtK1CRPj.mjs} +20 -16
  34. package/dist/{downgrade-Dqm0rJMy.mjs → downgrade-BXmQMkMy.mjs} +2 -2
  35. package/dist/feedback-DWVUMrxy.mjs +72 -0
  36. package/dist/feedback-evals-k0sFDdms.mjs +146 -0
  37. package/dist/{golden-evaluations-K45fcWGW.mjs → golden-evaluations-De4KwoYr.mjs} +3 -3
  38. package/dist/index.d.mts +236 -9
  39. package/dist/index.mjs +14 -11
  40. package/dist/{init-jDQSo-Or.mjs → init-CCV2eRNB.mjs} +3 -3
  41. package/dist/markdown-rendering.mjs +1 -1
  42. package/dist/{markdown-sections-7OoA7ylx.mjs → markdown-sections-BKy4labo.mjs} +110 -2
  43. package/dist/{mcp-CwgKlbIz.mjs → mcp-DsjWxF7g.mjs} +11 -10
  44. package/dist/mcp.d.mts +20 -5
  45. package/dist/mcp.mjs +538 -56
  46. package/dist/{metadata-D4v_R1t5.mjs → metadata-7htr-Xj_.mjs} +1 -1
  47. package/dist/{content-change-hydration-BsbBXAUi.mjs → openapi-mcp-DQ3xptKo.mjs} +182 -4
  48. package/dist/openapi-mcp-node-vO3IXVGI.mjs +43 -0
  49. package/dist/{package-version--oQiHwZj.mjs → package-version-Ci99L0yo.mjs} +1 -1
  50. package/dist/{prompt-references-DW5vG5zs.mjs → prompt-references-1Wce71c7.mjs} +3 -3
  51. package/dist/propose-DJOiHu9Y.mjs +73 -0
  52. package/dist/retrieval-digest-B-SgtXKK.mjs +177 -0
  53. package/dist/retrieval-digest-DyNRD-mt.d.mts +467 -0
  54. package/dist/{review-C_5wB0rR.mjs → review-XI9FXeos.mjs} +12 -12
  55. package/dist/{robots-NQesPX5L.mjs → robots-BOjWB1ky.mjs} +2 -2
  56. package/dist/{robots-BQ42Y84a.mjs → robots-CzKesbdX.mjs} +6 -6
  57. package/dist/{search-B5etKU-v.mjs → search-DwAoC9X7.mjs} +12 -11
  58. package/dist/server.d.mts +47 -264
  59. package/dist/server.mjs +14 -11
  60. package/dist/{sidebar-CwZlgyAz.d.mts → sidebar-CJze8SBi.d.mts} +1 -1
  61. package/dist/{sitemap-DjhxvQP1.mjs → sitemap-DOaJIKJf.mjs} +11 -10
  62. package/dist/{sitemap-server-B0SiREN0.mjs → sitemap-server-NeVu8W38.mjs} +11 -1
  63. package/dist/{skills-BFang8vF.mjs → skills-BILSESek.mjs} +11 -10
  64. package/dist/{standards-discovery-Ckx0tN7B.mjs → standards-discovery-Bpz90VYq.mjs} +2 -177
  65. package/dist/{standards-discovery-D1Ys4J3h.d.mts → standards-discovery-Xm84bS4M.d.mts} +1 -1
  66. package/dist/{types-DLLefoU5.d.mts → types-QhYMnKvb.d.mts} +148 -2
  67. package/dist/{upgrade-kj2crJSk.mjs → upgrade-B92U83xo.mjs} +2 -2
  68. package/package.json +1 -1
  69. package/dist/retrieval-digest-DYEsIeCE.d.mts +0 -164
  70. /package/dist/{audience-exposure-Cw3SA4WE.d.mts → audience-exposure-CkNZNeVX.d.mts} +0 -0
  71. /package/dist/{cloud-analytics-CSyFE6SS.mjs → cloud-analytics-Ci1yiwD8.mjs} +0 -0
  72. /package/dist/{config-BaTmjDqD.mjs → config-DrZ3fXgf.mjs} +0 -0
  73. /package/dist/{errors-DZiNXv5c.mjs → errors-BDmra6mz.mjs} +0 -0
  74. /package/dist/{i18n-B3BIxShC.mjs → i18n-B1gJ9EW0.mjs} +0 -0
  75. /package/dist/{sidebar-IcpElOVH.mjs → sidebar-3W51s2sF.mjs} +0 -0
  76. /package/dist/{templates-kc7jxspR.mjs → templates-DdB4BBGq.mjs} +0 -0
  77. /package/dist/{utils-DOMeZW9V.mjs → utils-Cc1SrVza.mjs} +0 -0
package/dist/mcp.mjs CHANGED
@@ -1,9 +1,11 @@
1
- import { a as emitDocsAnalyticsEvent, i as emitDocsAgentTraceEvent, n as createDocsAgentTraceContext, r as createDocsAgentTraceId } from "./analytics-Bx44lg6d.mjs";
2
- import { $n as resolveDocsMcpProtectedResourceMetadataLocation, An as normalizeAgentLocale, Dn as agentVersionConstraintMatches, E as DOCS_MARKDOWN_SECTION_INDEX_FORMAT, Fn as paginateDocsItems, Jn as isDocsMcpProtectedResourceMetadataPath, Jt as resolveDocsContentChangesConfig, Kt as isDocsContentChangeGeneration, Ln as normalizeDocsRelated, Nn as DocsPaginationCursorError, On as agentVersionConstraintsOverlap, P as buildDocsMarkdownSectionIndex, Qn as normalizeDocsMcpEndpointPath, Rn as renderDocsRelatedMarkdownLines, Tn as resolveDocsAudienceMdxContent, Ut as DocsContentChangesRequestError, Wt as createDocsContentChangeFeed, Xn as isDocsMcpResourcePath, Zn as normalizeDocsMcpAuthorizationServerUrls, dn as performDocsSearch, er as resolveDocsMcpResourceLocation, fn as performDocsSearchWithMetadata, jn as normalizeAgentScopeValues, kn as normalizeAgentFramework, qn as isDocsMcpOAuthScopeToken, tn as buildDocsSearchFacets, un as normalizeDocsSearchFilters, wt as toDocsMarkdownUrl, yn as resolveLocalDocsMcpSearchConfig } from "./agent-CFqECAG0.mjs";
3
- import { S as upsertPageAgentContractMarkdown, _ as hasStructuredPageAgentContract, o as findDocsMarkdownSection, s as parseDocsMarkdownSections, v as normalizePageAgentFrontmatter, y as renderPageAgentContractMarkdown } from "./markdown-sections-7OoA7ylx.mjs";
4
- import { q as isDocsRetrievalCanonicalUrl } from "./standards-discovery-Ckx0tN7B.mjs";
5
- import { a as hydrateDocsContentChanges, f as stripGeneratedAgentProvenance, g as emitDocsTelemetryProjectEvent, h as emitDocsTelemetryMcpToolEvent, i as MIN_DOCS_CONTENT_CHANGE_HYDRATION_TOKEN_BUDGET, n as DOCS_CONTENT_CHANGE_HYDRATION_FORMAT, p as emitDocsTelemetryAgentSurfaceEvent, r as MAX_DOCS_CONTENT_CHANGE_HYDRATION_TOKEN_BUDGET, t as DEFAULT_DOCS_CONTENT_CHANGE_HYDRATION_TOKEN_BUDGET } from "./content-change-hydration-BsbBXAUi.mjs";
6
- import { n as resolvePageSidebarFolderIndexBehavior } from "./sidebar-IcpElOVH.mjs";
1
+ import { C as filterDocsPagesByAccess, S as upsertPageAgentContractMarkdown, _ as hasStructuredPageAgentContract, o as findDocsMarkdownSection, s as parseDocsMarkdownSections, v as normalizePageAgentFrontmatter, y as renderPageAgentContractMarkdown } from "./markdown-sections-BKy4labo.mjs";
2
+ import { a as emitDocsAnalyticsEvent, i as emitDocsAgentTraceEvent, n as createDocsAgentTraceContext, r as createDocsAgentTraceId } from "./analytics-CqfcIBdg.mjs";
3
+ import { $n as normalizeDocsMcpEndpointPath, An as normalizeAgentFramework, E as DOCS_MARKDOWN_SECTION_INDEX_FORMAT, En as resolveDocsAudienceMdxContent, Et as validateDocsAgentFeedbackPayload, Gt as createDocsContentChangeFeed, In as paginateDocsItems, Jn as isDocsMcpOAuthScopeToken, Mn as normalizeAgentScopeValues, On as agentVersionConstraintMatches, P as buildDocsMarkdownSectionIndex, Pn as DocsPaginationCursorError, Qn as normalizeDocsMcpAuthorizationServerUrls, Rn as normalizeDocsRelated, Tt as toDocsMarkdownUrl, Wt as DocsContentChangesRequestError, Yn as isDocsMcpProtectedResourceMetadataPath, Yt as resolveDocsContentChangesConfig, Zn as isDocsMcpResourcePath, ar as buildDocsOkfBundle, bn as resolveLocalDocsMcpSearchConfig, dn as normalizeDocsSearchFilters, er as resolveDocsMcpProtectedResourceMetadataLocation, fn as performDocsSearch, jn as normalizeAgentLocale, kn as agentVersionConstraintsOverlap, nn as buildDocsSearchFacets, or as normalizeDocsOkfTrustMetadataInput, pn as performDocsSearchWithMetadata, qt as isDocsContentChangeGeneration, sr as resolveDocsOkfConfig, tr as resolveDocsMcpResourceLocation, ut as resolveDocsAgentFeedbackConfig, zn as renderDocsRelatedMarkdownLines } from "./agent-DCTGj6J7.mjs";
4
+ import { r as isDocsRetrievalCanonicalUrl } from "./retrieval-digest-B-SgtXKK.mjs";
5
+ import "./standards-discovery-Bpz90VYq.mjs";
6
+ import { _ as emitDocsTelemetryAgentSurfaceEvent, a as DEFAULT_DOCS_CONTENT_CHANGE_HYDRATION_TOKEN_BUDGET, b as emitDocsTelemetryProjectEvent, c as MIN_DOCS_CONTENT_CHANGE_HYDRATION_TOKEN_BUDGET, g as stripGeneratedAgentProvenance, i as resolveDocsOpenApiMcpOperations, l as hydrateDocsContentChanges, n as readDocsOpenApiMcpResponse, o as DOCS_CONTENT_CHANGE_HYDRATION_FORMAT, r as resolveDocsOpenApiMcpBaseUrl, s as MAX_DOCS_CONTENT_CHANGE_HYDRATION_TOKEN_BUDGET, t as acquireDocsOpenApiMcpBudget, y as emitDocsTelemetryMcpToolEvent } from "./openapi-mcp-DQ3xptKo.mjs";
7
+ import { n as resolvePageSidebarFolderIndexBehavior } from "./sidebar-3W51s2sF.mjs";
8
+ import { t as validateDocsOpenApiMcpUrl } from "./openapi-mcp-node-vO3IXVGI.mjs";
7
9
  import matter from "gray-matter";
8
10
  import fs from "node:fs";
9
11
  import path from "node:path";
@@ -12,6 +14,23 @@ import { InMemoryServerEventBus, McpServer, ProtocolError, ProtocolErrorCode, Re
12
14
  import * as z from "zod/v4";
13
15
 
14
16
  //#region src/mcp.ts
17
+ function filterDocsMcpNavigation(tree, allowedUrls) {
18
+ const filterNodes = (nodes) => nodes.flatMap((node) => {
19
+ if (node.type === "page") return allowedUrls.has(node.url) ? [node] : [];
20
+ const children = filterNodes(node.children);
21
+ const index = node.index && allowedUrls.has(node.index.url) ? node.index : void 0;
22
+ const { index: _unfilteredIndex, ...folder } = node;
23
+ return children.length > 0 || index ? [{
24
+ ...folder,
25
+ ...index ? { index } : {},
26
+ children
27
+ }] : [];
28
+ });
29
+ return {
30
+ ...tree,
31
+ children: filterNodes(tree.children)
32
+ };
33
+ }
15
34
  const DEFAULT_MCP_VERSION = "0.0.0";
16
35
  const DEFAULT_MCP_NAME = "@farming-labs/docs";
17
36
  const DOCS_MCP_DISCOVERY_CACHE_TTL_MS = 300 * 1e3;
@@ -20,6 +39,8 @@ const DOCS_MCP_RESOURCE_CACHE_TTL_MS = 60 * 1e3;
20
39
  const DEFAULT_MCP_CONTEXT_TOKEN_BUDGET = 4e3;
21
40
  const MIN_MCP_CONTEXT_TOKEN_BUDGET = 256;
22
41
  const MAX_MCP_CONTEXT_TOKEN_BUDGET = 32e3;
42
+ const DEFAULT_MCP_READ_PAGES_TOKEN_BUDGET = 8e3;
43
+ const MAX_MCP_READ_PAGES_COUNT = 20;
23
44
  const DOCS_MCP_PROTOCOL_LIST_PAGE_SIZE = 10;
24
45
  const DOCS_MCP_TOOL_LIST_PAGE_SIZE = 25;
25
46
  const DOCS_MCP_PAGINATION_META_KEY = "dev.farming-labs/pagination";
@@ -233,6 +254,53 @@ const DOCS_CONFIG_SCHEMA_OPTIONS_TEMPLATE = [
233
254
  description: "Agent synchronization, reusable skills, compaction defaults, and offline-by-default usefulness evaluations.",
234
255
  docs: "/docs/getting-started/agent-ready-docs",
235
256
  children: [
257
+ {
258
+ path: "agent.okf",
259
+ name: "okf",
260
+ type: "boolean | DocsOkfConfig",
261
+ default: false,
262
+ description: "Publish Open Knowledge Format v0.2 source, generation, verification, lifecycle, and staleness metadata.",
263
+ children: [
264
+ {
265
+ path: "agent.okf.route",
266
+ name: "route",
267
+ type: "string",
268
+ default: "/.well-known/okf.json",
269
+ description: "Public route used by static Agent Bundle export."
270
+ },
271
+ {
272
+ path: "agent.okf.generatedBy",
273
+ name: "generatedBy",
274
+ type: "string",
275
+ default: "software:@farming-labs/docs",
276
+ description: "Generator actor used when a page omits okf.generated."
277
+ },
278
+ {
279
+ path: "agent.okf.staleAfterDays",
280
+ name: "staleAfterDays",
281
+ type: "number",
282
+ description: "Derive stale_after this many days after the best page timestamp."
283
+ },
284
+ {
285
+ path: "agent.okf.sources",
286
+ name: "sources",
287
+ type: "readonly DocsOkfSource[]",
288
+ description: "Default source provenance inherited by pages without authored sources."
289
+ },
290
+ {
291
+ path: "agent.okf.verified",
292
+ name: "verified",
293
+ type: "readonly DocsOkfActorTimestamp[]",
294
+ description: "Default machine or human verification records."
295
+ },
296
+ {
297
+ path: "agent.okf.status",
298
+ name: "status",
299
+ type: "\"draft\" | \"stable\" | \"deprecated\"",
300
+ description: "Default lifecycle status for knowledge documents."
301
+ }
302
+ ]
303
+ },
236
304
  {
237
305
  path: "agent.contentChanges",
238
306
  name: "contentChanges",
@@ -1380,40 +1448,55 @@ const DOCS_CONFIG_SCHEMA_OPTIONS_TEMPLATE = [
1380
1448
  path: "pageActions",
1381
1449
  name: "pageActions",
1382
1450
  type: "PageActionsConfig",
1383
- description: "Copy Markdown and Open in LLM actions for docs pages.",
1451
+ description: "Copy, Open in LLM, MCP connection, and Agent Skills setup actions.",
1384
1452
  docs: "/docs/customization/page-actions",
1385
- children: [{
1386
- path: "pageActions.copyMarkdown",
1387
- name: "copyMarkdown",
1388
- type: "boolean | PageActionConfig",
1389
- description: "Show a Copy Markdown action for the current page."
1390
- }, {
1391
- path: "pageActions.openDocs",
1392
- name: "openDocs",
1393
- type: "boolean | OpenDocsActionConfig",
1394
- description: "Show provider actions that open the current docs page in an LLM.",
1395
- children: [
1396
- {
1397
- path: "pageActions.openDocs.target",
1398
- name: "target",
1399
- type: "\"page\" | \"markdown\"",
1400
- default: "page",
1401
- description: "Whether provider URLs receive the rendered page URL or the .md markdown route."
1402
- },
1403
- {
1404
- path: "pageActions.openDocs.providers",
1405
- name: "providers",
1406
- type: "Array<string | PromptProviderConfig>",
1407
- description: "Provider IDs or provider objects. Built-ins include chatgpt, claude, cursor, and t3."
1408
- },
1409
- {
1410
- path: "pageActions.openDocs.prompt",
1411
- name: "prompt",
1412
- type: "string",
1413
- description: "Prompt text prepended to the provider URL when opening docs."
1414
- }
1415
- ]
1416
- }]
1453
+ children: [
1454
+ {
1455
+ path: "pageActions.copyMarkdown",
1456
+ name: "copyMarkdown",
1457
+ type: "boolean | PageActionConfig",
1458
+ description: "Show a Copy Markdown action for the current page."
1459
+ },
1460
+ {
1461
+ path: "pageActions.openDocs",
1462
+ name: "openDocs",
1463
+ type: "boolean | OpenDocsActionConfig",
1464
+ description: "Show provider actions that open the current docs page in an LLM.",
1465
+ children: [
1466
+ {
1467
+ path: "pageActions.openDocs.target",
1468
+ name: "target",
1469
+ type: "\"page\" | \"markdown\"",
1470
+ default: "page",
1471
+ description: "Whether provider URLs receive the rendered page URL or the .md markdown route."
1472
+ },
1473
+ {
1474
+ path: "pageActions.openDocs.providers",
1475
+ name: "providers",
1476
+ type: "Array<string | PromptProviderConfig>",
1477
+ description: "Provider IDs or provider objects. Built-ins include chatgpt, claude, cursor, and t3."
1478
+ },
1479
+ {
1480
+ path: "pageActions.openDocs.prompt",
1481
+ name: "prompt",
1482
+ type: "string",
1483
+ description: "Prompt text prepended to the provider URL when opening docs."
1484
+ }
1485
+ ]
1486
+ },
1487
+ {
1488
+ path: "pageActions.connectMcp",
1489
+ name: "connectMcp",
1490
+ type: "boolean | PageActionConnectMcpConfig",
1491
+ description: "Show copyable MCP setup for Claude Code, Cursor, VS Code, Codex, or a raw endpoint."
1492
+ },
1493
+ {
1494
+ path: "pageActions.installSkills",
1495
+ name: "installSkills",
1496
+ type: "boolean | PageActionInstallSkillsConfig",
1497
+ description: "Discover the published Agent Skills index and provide a copyable skills install command."
1498
+ }
1499
+ ]
1417
1500
  },
1418
1501
  {
1419
1502
  path: "ai",
@@ -1778,6 +1861,20 @@ const DOCS_CONFIG_SCHEMA_OPTIONS_TEMPLATE = [
1778
1861
  default: true,
1779
1862
  description: "Expose the read_page tool."
1780
1863
  },
1864
+ {
1865
+ path: "mcp.tools.readPages",
1866
+ name: "readPages",
1867
+ type: "boolean",
1868
+ default: true,
1869
+ description: "Expose the budget-aware read_pages batch tool."
1870
+ },
1871
+ {
1872
+ path: "mcp.tools.submitFeedback",
1873
+ name: "submitFeedback",
1874
+ type: "boolean",
1875
+ default: true,
1876
+ description: "Expose submit_feedback when feedback.agent is enabled and validate payloads with its configured schema."
1877
+ },
1781
1878
  {
1782
1879
  path: "mcp.tools.getCodeExamples",
1783
1880
  name: "getCodeExamples",
@@ -1798,6 +1895,13 @@ const DOCS_CONFIG_SCHEMA_OPTIONS_TEMPLATE = [
1798
1895
  type: "boolean",
1799
1896
  default: true,
1800
1897
  description: "Expose deterministic get_context retrieval with a conservative UTF-8 byte ceiling."
1898
+ },
1899
+ {
1900
+ path: "mcp.tools.getTrustMetadata",
1901
+ name: "getTrustMetadata",
1902
+ type: "boolean",
1903
+ default: true,
1904
+ description: "Expose OKF v0.2 trust metadata when agent.okf is enabled."
1801
1905
  }
1802
1906
  ]
1803
1907
  }
@@ -1828,6 +1932,47 @@ const DOCS_CONFIG_SCHEMA_OPTIONS_TEMPLATE = [
1828
1932
  name: "catalogTargets",
1829
1933
  type: "string[]",
1830
1934
  description: "Product API base URLs that the OpenAPI document describes in the RFC 9727 catalog."
1935
+ },
1936
+ {
1937
+ path: "apiReference.mcp",
1938
+ name: "mcp",
1939
+ type: "boolean | DocsOpenApiMcpConfig",
1940
+ default: false,
1941
+ description: "Project explicitly allowlisted OpenAPI operations into server-executed MCP tools.",
1942
+ children: [
1943
+ {
1944
+ path: "apiReference.mcp.operations",
1945
+ name: "operations",
1946
+ type: "readonly string[]",
1947
+ description: "Allowed operationIds or METHOD /path selectors; empty exposes nothing."
1948
+ },
1949
+ {
1950
+ path: "apiReference.mcp.baseUrl",
1951
+ name: "baseUrl",
1952
+ type: "string",
1953
+ description: "Override the first OpenAPI server URL used for tool requests."
1954
+ },
1955
+ {
1956
+ path: "apiReference.mcp.allowMutations",
1957
+ name: "allowMutations",
1958
+ type: "boolean",
1959
+ default: false,
1960
+ description: "Permit explicitly allowlisted write operations."
1961
+ },
1962
+ {
1963
+ path: "apiReference.mcp.headers",
1964
+ name: "headers",
1965
+ type: "DocsOpenApiMcpHeaders",
1966
+ description: "Server-owned credential headers applied after model-provided input."
1967
+ },
1968
+ {
1969
+ path: "apiReference.mcp.timeoutMs",
1970
+ name: "timeoutMs",
1971
+ type: "number",
1972
+ default: 1e4,
1973
+ description: "Per-operation HTTP timeout."
1974
+ }
1975
+ ]
1831
1976
  }
1832
1977
  ]
1833
1978
  },
@@ -1996,6 +2141,30 @@ const readPageInputSchema = z.object({
1996
2141
  section: z.string().trim().min(1).optional(),
1997
2142
  maxChars: z.number().int().min(256).max(1e6).optional()
1998
2143
  });
2144
+ const trustMetadataInputSchema = z.object({
2145
+ path: z.string().trim().min(1).optional(),
2146
+ locale: z.string().trim().min(1).max(128).optional()
2147
+ });
2148
+ const openApiOperationInputSchema = z.object({
2149
+ parameters: z.record(z.string(), z.unknown()).optional(),
2150
+ body: z.unknown().optional()
2151
+ });
2152
+ const readPagesInputSchema = z.object({
2153
+ paths: z.array(z.string().trim().min(1)).min(1).max(MAX_MCP_READ_PAGES_COUNT).describe("Page slugs or URL paths to read in the requested order."),
2154
+ locale: z.string().trim().min(1).max(128).optional(),
2155
+ tokenBudget: z.number().int().min(MIN_MCP_CONTEXT_TOKEN_BUDGET).max(MAX_MCP_CONTEXT_TOKEN_BUDGET).optional(),
2156
+ maxCharsPerPage: z.number().int().min(256).max(1e6).optional()
2157
+ });
2158
+ const submitFeedbackInputSchema = z.object({
2159
+ context: z.object({
2160
+ page: z.string().optional(),
2161
+ url: z.string().optional(),
2162
+ slug: z.string().optional(),
2163
+ locale: z.string().optional(),
2164
+ source: z.string().optional()
2165
+ }).optional(),
2166
+ payload: z.record(z.string(), z.unknown())
2167
+ });
1999
2168
  const listPageSectionsInputSchema = z.object({
2000
2169
  path: z.string().min(1),
2001
2170
  locale: z.string().trim().min(1).max(128).optional(),
@@ -2304,7 +2473,8 @@ const searchResultOutputSchema = z.object({
2304
2473
  ]),
2305
2474
  score: z.number().optional(),
2306
2475
  section: z.string().optional(),
2307
- source: retrievalSourceOutputSchema.optional()
2476
+ source: retrievalSourceOutputSchema.optional(),
2477
+ trust: z.record(z.string(), z.unknown()).optional()
2308
2478
  });
2309
2479
  const searchFiltersOutputSchema = z.object({
2310
2480
  framework: z.array(z.string()).optional(),
@@ -2522,6 +2692,46 @@ const readPageOutputSchema = z.object({
2522
2692
  totalChars: z.number().int().nonnegative(),
2523
2693
  truncated: z.boolean()
2524
2694
  });
2695
+ const trustMetadataOutputSchema = z.object({
2696
+ format: z.literal("open-knowledge-format.v0.2"),
2697
+ spec_version: z.literal("0.2"),
2698
+ generated: z.object({
2699
+ by: z.string(),
2700
+ at: z.string()
2701
+ }),
2702
+ documents: z.array(z.record(z.string(), z.unknown()))
2703
+ });
2704
+ const openApiOperationOutputSchema = z.object({
2705
+ operationId: z.string(),
2706
+ status: z.number().int(),
2707
+ ok: z.boolean(),
2708
+ contentType: z.string().optional(),
2709
+ responseTruncated: z.boolean().optional(),
2710
+ body: z.unknown()
2711
+ });
2712
+ const readPagesOutputSchema = z.object({
2713
+ format: z.literal("docs-read-pages.v1"),
2714
+ budget: z.object({
2715
+ requestedTokens: z.number().int().positive(),
2716
+ strategy: z.literal("utf8-bytes"),
2717
+ maxUtf8Bytes: z.number().int().positive(),
2718
+ usedUtf8Bytes: z.number().int().nonnegative(),
2719
+ remainingUtf8Bytes: z.number().int().nonnegative(),
2720
+ truncated: z.boolean()
2721
+ }),
2722
+ resultCount: z.number().int().nonnegative(),
2723
+ requestedCount: z.number().int().positive(),
2724
+ pages: z.array(readPageOutputSchema.extend({ requestedPath: z.string() })),
2725
+ errors: z.array(z.object({
2726
+ path: z.string(),
2727
+ error: z.string()
2728
+ })),
2729
+ remainingPaths: z.array(z.string())
2730
+ });
2731
+ const submitFeedbackOutputSchema = z.object({
2732
+ accepted: z.literal(true),
2733
+ message: z.string()
2734
+ });
2525
2735
  const pageSectionMetadataOutputSchema = z.object({
2526
2736
  id: z.string(),
2527
2737
  heading: z.string(),
@@ -2609,6 +2819,8 @@ function resolveDocsMcpConfig(mcp, defaults = {}) {
2609
2819
  listPages: true,
2610
2820
  listPageSections: true,
2611
2821
  readPage: true,
2822
+ readPages: true,
2823
+ submitFeedback: true,
2612
2824
  listTasks: true,
2613
2825
  readTask: true,
2614
2826
  searchDocs: true,
@@ -2618,7 +2830,8 @@ function resolveDocsMcpConfig(mcp, defaults = {}) {
2618
2830
  getNavigation: true,
2619
2831
  getCodeExamples: true,
2620
2832
  getConfigSchema: true,
2621
- getContext: true
2833
+ getContext: true,
2834
+ getTrustMetadata: true
2622
2835
  },
2623
2836
  prompts: resolveDocsMcpPromptsConfig(),
2624
2837
  security: resolveDocsMcpSecurityConfig()
@@ -2634,6 +2847,8 @@ function resolveDocsMcpConfig(mcp, defaults = {}) {
2634
2847
  listPages: config.tools?.listPages ?? true,
2635
2848
  listPageSections: config.tools?.listPageSections ?? true,
2636
2849
  readPage: config.tools?.readPage ?? true,
2850
+ readPages: config.tools?.readPages ?? true,
2851
+ submitFeedback: config.tools?.submitFeedback ?? true,
2637
2852
  listTasks: config.tools?.listTasks ?? true,
2638
2853
  readTask: config.tools?.readTask ?? true,
2639
2854
  searchDocs: config.tools?.searchDocs ?? true,
@@ -2643,7 +2858,8 @@ function resolveDocsMcpConfig(mcp, defaults = {}) {
2643
2858
  getNavigation: config.tools?.getNavigation ?? true,
2644
2859
  getCodeExamples: config.tools?.getCodeExamples ?? true,
2645
2860
  getConfigSchema: config.tools?.getConfigSchema ?? true,
2646
- getContext: config.tools?.getContext ?? true
2861
+ getContext: config.tools?.getContext ?? true,
2862
+ getTrustMetadata: config.tools?.getTrustMetadata ?? true
2647
2863
  },
2648
2864
  prompts: resolveDocsMcpPromptsConfig(config.prompts),
2649
2865
  security: resolveDocsMcpSecurityConfig(config.security)
@@ -3078,6 +3294,7 @@ async function createDocsMcpServer(options) {
3078
3294
  });
3079
3295
  const contentChangesEnabled = resolveDocsContentChangesConfig(options.contentChanges).enabled && resolved.tools.listContentChanges !== false;
3080
3296
  const contentChangeHydrationEnabled = contentChangesEnabled && resolved.tools.hydrateContentChanges !== false;
3297
+ const agentFeedback = resolveDocsAgentFeedbackConfig(options.feedback);
3081
3298
  const cacheScope = options.requestContext?.auth ? "private" : "public";
3082
3299
  const server = new McpServer({
3083
3300
  name: resolved.name,
@@ -3122,11 +3339,16 @@ async function createDocsMcpServer(options) {
3122
3339
  search: options.search
3123
3340
  };
3124
3341
  const telemetryFramework = options.telemetryFramework ?? "mcp";
3125
- function getSourcePages(locale) {
3126
- return options.source.getPages(resolveSourceLocale(locale), options.requestContext);
3342
+ async function getSourcePages(locale) {
3343
+ return getResolvedSourcePages(resolveSourceLocale(locale));
3127
3344
  }
3128
- function getSourceNavigation(locale) {
3129
- return options.source.getNavigation(resolveSourceLocale(locale), options.requestContext);
3345
+ async function getResolvedSourcePages(locale) {
3346
+ return filterDocsPagesByAccess(await options.source.getPages(locale, options.requestContext), options.requestContext?.auth);
3347
+ }
3348
+ async function getSourceNavigation(locale) {
3349
+ const resolvedLocale = resolveSourceLocale(locale);
3350
+ const [tree, pages] = await Promise.all([options.source.getNavigation(resolvedLocale, options.requestContext), getResolvedSourcePages(resolvedLocale)]);
3351
+ return filterDocsMcpNavigation(tree, new Set(pages.map((page) => page.url)));
3130
3352
  }
3131
3353
  function resolveSourceLocale(locale) {
3132
3354
  return options.source.resolveLocale ? options.source.resolveLocale(locale, options.requestContext) : locale;
@@ -3146,10 +3368,157 @@ async function createDocsMcpServer(options) {
3146
3368
  const defaultPages = dedupePages(await getSourcePages());
3147
3369
  const defaultTree = await getSourceNavigation();
3148
3370
  const defaultSkills = await getSourceSkills();
3371
+ const okfConfig = resolveDocsOkfConfig(options.okf);
3372
+ const openapiDocument = options.openapi ? typeof options.openapi.document === "function" ? await options.openapi.document() : options.openapi.document : void 0;
3373
+ const openapiConfig = options.openapi?.config;
3374
+ const openapiOperations = openapiDocument && openapiConfig ? resolveDocsOpenApiMcpOperations(openapiDocument, openapiConfig) : [];
3149
3375
  const prompts = resolved.prompts ?? resolveDocsMcpPromptsConfig();
3150
3376
  const contractPromptDefinitions = prompts.enabled ? resolveDocsMcpContractPromptDefinitions(defaultPages, prompts.contracts, options.source.entry) : [];
3151
3377
  const goldenPromptDefinitions = prompts.enabled ? resolveDocsMcpGoldenPromptDefinitions(prompts.goldenTasks, options.evaluations) : [];
3152
3378
  if (contractPromptDefinitions.length > 0 || goldenPromptDefinitions.length > 0) server.server.registerCapabilities({ prompts: { listChanged: false } });
3379
+ if (okfConfig.enabled && resolved.tools.getTrustMetadata !== false) registerTool("get_trust_metadata", {
3380
+ title: "Get documentation trust metadata",
3381
+ description: "Return OKF v0.2 sources, generation, verification, lifecycle status, trust tier, and staleness for one page or the docs corpus.",
3382
+ inputSchema: trustMetadataInputSchema,
3383
+ outputSchema: trustMetadataOutputSchema,
3384
+ annotations: { readOnlyHint: true }
3385
+ }, async ({ path: requestedPath, locale }) => {
3386
+ const pages = dedupePages(await getSourcePages(locale));
3387
+ const selected = requestedPath ? pages.filter((page) => Boolean(findDocsPage([page], requestedPath, options.source.entry))) : pages;
3388
+ if (requestedPath && selected.length === 0) return {
3389
+ content: [{
3390
+ type: "text",
3391
+ text: `No docs page matched "${requestedPath}".`
3392
+ }],
3393
+ isError: true
3394
+ };
3395
+ const bundle = buildDocsOkfBundle(selected, okfConfig);
3396
+ trackMcpTool("get_trust_metadata", {
3397
+ locale,
3398
+ resultCount: bundle.documents.length
3399
+ });
3400
+ return createStructuredTextResult(bundle);
3401
+ });
3402
+ for (const operation of openapiOperations) registerTool(operation.toolName, {
3403
+ title: operation.title,
3404
+ description: operation.description ?? `${operation.method} ${operation.path} (${operation.operationId})`,
3405
+ inputSchema: openApiOperationInputSchema,
3406
+ outputSchema: openApiOperationOutputSchema,
3407
+ annotations: {
3408
+ readOnlyHint: operation.readOnly,
3409
+ destructiveHint: operation.destructive,
3410
+ idempotentHint: operation.idempotent,
3411
+ openWorldHint: true
3412
+ },
3413
+ _meta: { "dev.farming-labs/openapi": {
3414
+ operationId: operation.operationId,
3415
+ method: operation.method,
3416
+ path: operation.path,
3417
+ security: operation.security,
3418
+ securitySchemes: operation.securitySchemes
3419
+ } }
3420
+ }, async ({ parameters = {}, body }) => {
3421
+ if (!openapiDocument || !openapiConfig) throw new ProtocolError(ProtocolErrorCode.InternalError, "OpenAPI MCP is unavailable.");
3422
+ const baseUrl = resolveDocsOpenApiMcpBaseUrl(openapiDocument, openapiConfig);
3423
+ if (!baseUrl) throw new ProtocolError(ProtocolErrorCode.InvalidParams, `OpenAPI operation ${operation.operationId} has no server URL. Configure apiReference.mcp.baseUrl.`);
3424
+ let requestPath = operation.path;
3425
+ const query = new URLSearchParams();
3426
+ const requestHeaders = new Headers({ Accept: "application/json, text/plain, */*" });
3427
+ const requestCookies = new URLSearchParams();
3428
+ for (const parameter of operation.parameters) {
3429
+ const value = parameters[parameter.name];
3430
+ if (value === void 0 || value === null || value === "") {
3431
+ if (parameter.required) return {
3432
+ content: [{
3433
+ type: "text",
3434
+ text: `Missing required ${parameter.in} parameter: ${parameter.name}`
3435
+ }],
3436
+ isError: true
3437
+ };
3438
+ continue;
3439
+ }
3440
+ const values = Array.isArray(value) ? value : [value];
3441
+ if (parameter.in === "path") requestPath = requestPath.replaceAll(`{${parameter.name}}`, encodeURIComponent(String(values[0])));
3442
+ else if (parameter.in === "query") for (const item of values) query.append(parameter.name, String(item));
3443
+ else if (parameter.in === "header") requestHeaders.set(parameter.name, String(values[0]));
3444
+ else if (parameter.in === "cookie") requestCookies.set(parameter.name, String(values[0]));
3445
+ }
3446
+ const requestUrl = new URL(requestPath.replace(/^\/+/, ""), `${baseUrl.replace(/\/+$/u, "")}/`);
3447
+ for (const [name, value] of query) requestUrl.searchParams.append(name, value);
3448
+ try {
3449
+ await validateDocsOpenApiMcpUrl(requestUrl, openapiConfig);
3450
+ } catch (error) {
3451
+ throw new ProtocolError(ProtocolErrorCode.InvalidParams, error instanceof Error ? error.message : "OpenAPI MCP destination was blocked.");
3452
+ }
3453
+ const configuredHeaders = typeof openapiConfig.headers === "function" ? await openapiConfig.headers({
3454
+ operationId: operation.operationId,
3455
+ method: operation.method,
3456
+ path: operation.path,
3457
+ security: operation.security
3458
+ }) : openapiConfig.headers;
3459
+ for (const [name, value] of Object.entries(configuredHeaders ?? {})) requestHeaders.set(name, value);
3460
+ if (requestCookies.size > 0) requestHeaders.set("Cookie", [...requestCookies].map(([name, value]) => `${name}=${value}`).join("; "));
3461
+ if (body !== void 0) requestHeaders.set("Content-Type", "application/json");
3462
+ const controller = new AbortController();
3463
+ const timeout = setTimeout(() => controller.abort(), openapiConfig.timeoutMs ?? 1e4);
3464
+ const budgetKey = `${options.requestContext?.auth?.id ?? "anonymous"}:${operation.operationId}`;
3465
+ let releaseBudget;
3466
+ try {
3467
+ releaseBudget = acquireDocsOpenApiMcpBudget(budgetKey, openapiConfig);
3468
+ let response;
3469
+ let destination = requestUrl;
3470
+ const maxRedirects = Math.max(0, openapiConfig.maxRedirects ?? 0);
3471
+ for (let redirectCount = 0; redirectCount <= maxRedirects; redirectCount += 1) {
3472
+ response = await fetch(destination, {
3473
+ method: operation.method,
3474
+ headers: requestHeaders,
3475
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {},
3476
+ redirect: "manual",
3477
+ signal: controller.signal
3478
+ });
3479
+ if (![
3480
+ 301,
3481
+ 302,
3482
+ 303,
3483
+ 307,
3484
+ 308
3485
+ ].includes(response.status)) break;
3486
+ const location = response.headers.get("location");
3487
+ if (!location || redirectCount >= maxRedirects) throw new ProtocolError(ProtocolErrorCode.InvalidRequest, "OpenAPI MCP redirect blocked. Increase maxRedirects to follow validated redirects.");
3488
+ destination = new URL(location, destination);
3489
+ try {
3490
+ await validateDocsOpenApiMcpUrl(destination, openapiConfig);
3491
+ } catch (error) {
3492
+ throw new ProtocolError(ProtocolErrorCode.InvalidRequest, error instanceof Error ? error.message : "OpenAPI MCP redirect was blocked.");
3493
+ }
3494
+ }
3495
+ if (!response) throw new ProtocolError(ProtocolErrorCode.InternalError, "No API response.");
3496
+ const contentType = response.headers.get("content-type") ?? void 0;
3497
+ const { text, truncated } = await readDocsOpenApiMcpResponse(response, openapiConfig.maxResponseBytes);
3498
+ let responseBody = text;
3499
+ if (contentType?.includes("json") && text && !truncated) try {
3500
+ responseBody = JSON.parse(text);
3501
+ } catch {
3502
+ responseBody = text;
3503
+ }
3504
+ const result = {
3505
+ operationId: operation.operationId,
3506
+ status: response.status,
3507
+ ok: response.ok,
3508
+ ...contentType ? { contentType } : {},
3509
+ ...truncated ? { responseTruncated: true } : {},
3510
+ body: responseBody
3511
+ };
3512
+ trackMcpTool(operation.toolName, { resultCount: 1 });
3513
+ return {
3514
+ ...createStructuredTextResult(result),
3515
+ ...response.ok ? {} : { isError: true }
3516
+ };
3517
+ } finally {
3518
+ releaseBudget?.();
3519
+ clearTimeout(timeout);
3520
+ }
3521
+ });
3153
3522
  for (const definition of contractPromptDefinitions) {
3154
3523
  const { page, contract, resourceUri } = definition;
3155
3524
  server.registerPrompt(definition.name, {
@@ -3208,11 +3577,12 @@ async function createDocsMcpServer(options) {
3208
3577
  if (!contentChangesEnabled) throw new ProtocolError(ProtocolErrorCode.MethodNotFound, "Content changes are disabled.");
3209
3578
  if (since && !isDocsContentChangeGeneration(since)) throw new ProtocolError(ProtocolErrorCode.InvalidParams, "Content-change `since` must be a SHA-256 index generation.");
3210
3579
  const resolvedLocale = resolveSourceLocale(locale);
3211
- const pages = dedupePages(await options.source.getPages(resolvedLocale, options.requestContext));
3580
+ const pages = dedupePages(await getResolvedSourcePages(resolvedLocale));
3212
3581
  try {
3213
3582
  return {
3214
3583
  result: await contentChangeFeed.resolve({
3215
3584
  pages: toSearchSourcePages(pages),
3585
+ principal: options.requestContext?.auth,
3216
3586
  search: options.search,
3217
3587
  audience: "agent",
3218
3588
  locale: resolvedLocale,
@@ -3400,7 +3770,7 @@ async function createDocsMcpServer(options) {
3400
3770
  metadata: { tool: "list_pages" }
3401
3771
  });
3402
3772
  try {
3403
- const page = paginateDocsMcpItems(toPageSummaries(dedupePages(await options.source.getPages(resolvedLocale, options.requestContext))).sort(compareDocsMcpPageSummaries), {
3773
+ const page = paginateDocsMcpItems(toPageSummaries(dedupePages(await getResolvedSourcePages(resolvedLocale))).sort(compareDocsMcpPageSummaries), {
3404
3774
  kind: "mcp.tool/list_pages",
3405
3775
  scope: stableDocsMcpPaginationScope({ locale: resolvedLocale ?? null }),
3406
3776
  cursor
@@ -3495,7 +3865,7 @@ async function createDocsMcpServer(options) {
3495
3865
  metadata: { tool: "list_docs" }
3496
3866
  });
3497
3867
  try {
3498
- const docs = paginateDocsMcpDocsList(listDocsBySection(dedupePages(await options.source.getPages(resolvedLocale, options.requestContext)), {
3868
+ const docs = paginateDocsMcpDocsList(listDocsBySection(dedupePages(await getResolvedSourcePages(resolvedLocale)), {
3499
3869
  section,
3500
3870
  entry: options.source.entry
3501
3871
  }), {
@@ -3593,7 +3963,7 @@ async function createDocsMcpServer(options) {
3593
3963
  metadata: { tool: "list_tasks" }
3594
3964
  });
3595
3965
  try {
3596
- const page = paginateDocsMcpItems(listDocsTasks(dedupePages(await options.source.getPages(resolvedLocale, options.requestContext)), {
3966
+ const page = paginateDocsMcpItems(listDocsTasks(dedupePages(await getResolvedSourcePages(resolvedLocale)), {
3597
3967
  query,
3598
3968
  framework,
3599
3969
  version,
@@ -4017,7 +4387,8 @@ async function createDocsMcpServer(options) {
4017
4387
  });
4018
4388
  try {
4019
4389
  const facets = await buildDocsSearchFacets({
4020
- pages: toSearchSourcePages(dedupePages(await options.source.getPages(resolvedLocale, options.requestContext))),
4390
+ pages: toSearchSourcePages(dedupePages(await getResolvedSourcePages(resolvedLocale))),
4391
+ principal: options.requestContext?.auth,
4021
4392
  search: toolSearchConfig ?? true,
4022
4393
  audience: resolvedAudience,
4023
4394
  filters,
@@ -4125,7 +4496,8 @@ async function createDocsMcpServer(options) {
4125
4496
  });
4126
4497
  try {
4127
4498
  const searchResponse = await performDocsSearchWithMetadata({
4128
- pages: toSearchSourcePages(dedupePages(await options.source.getPages(resolvedLocale, options.requestContext))),
4499
+ pages: toSearchSourcePages(dedupePages(await getResolvedSourcePages(resolvedLocale))),
4500
+ principal: options.requestContext?.auth,
4129
4501
  query,
4130
4502
  search: toolSearchConfig ?? true,
4131
4503
  audience: resolvedAudience,
@@ -4331,7 +4703,8 @@ async function createDocsMcpServer(options) {
4331
4703
  });
4332
4704
  try {
4333
4705
  const result = await buildDocsMcpContext({
4334
- pages: dedupePages(await options.source.getPages(resolvedLocale, options.requestContext)),
4706
+ pages: dedupePages(await getResolvedSourcePages(resolvedLocale)),
4707
+ principal: options.requestContext?.auth,
4335
4708
  query,
4336
4709
  framework,
4337
4710
  version,
@@ -4439,7 +4812,7 @@ async function createDocsMcpServer(options) {
4439
4812
  metadata: { tool: "list_page_sections" }
4440
4813
  });
4441
4814
  try {
4442
- const page = findDocsPage(dedupePages(await options.source.getPages(resolvedLocale, options.requestContext)), requestedPath, options.source.entry);
4815
+ const page = findDocsPage(dedupePages(await getResolvedSourcePages(resolvedLocale)), requestedPath, options.source.entry);
4443
4816
  if (!page) {
4444
4817
  const elapsed = durationMs(startedAt);
4445
4818
  await emitDocsAnalyticsEvent(options.analytics, {
@@ -4580,6 +4953,111 @@ async function createDocsMcpServer(options) {
4580
4953
  throw error;
4581
4954
  }
4582
4955
  });
4956
+ if (resolved.tools.readPages !== false) registerTool("read_pages", {
4957
+ title: "Read several docs pages",
4958
+ description: "Read up to 20 documentation pages in one round trip. Results preserve request order and share a conservative UTF-8 token budget.",
4959
+ inputSchema: readPagesInputSchema,
4960
+ outputSchema: readPagesOutputSchema,
4961
+ annotations: { readOnlyHint: true }
4962
+ }, async ({ paths: requestedPaths, locale, tokenBudget = DEFAULT_MCP_READ_PAGES_TOKEN_BUDGET, maxCharsPerPage }) => {
4963
+ const pages = dedupePages(await getSourcePages(locale));
4964
+ const maxUtf8Bytes = tokenBudget * 4;
4965
+ let usedUtf8Bytes = 0;
4966
+ let processedCount = 0;
4967
+ let truncated = false;
4968
+ const results = [];
4969
+ const errors = [];
4970
+ for (const requestedPath of requestedPaths) {
4971
+ if (usedUtf8Bytes >= maxUtf8Bytes) {
4972
+ truncated = true;
4973
+ break;
4974
+ }
4975
+ processedCount += 1;
4976
+ const page = findDocsPage(pages, requestedPath, options.source.entry);
4977
+ if (!page) {
4978
+ errors.push({
4979
+ path: requestedPath,
4980
+ error: `No docs page matched "${requestedPath}".`
4981
+ });
4982
+ continue;
4983
+ }
4984
+ const fullDocument = renderPageDocument(page);
4985
+ const perPage = limitDocsMcpText(fullDocument, maxCharsPerPage);
4986
+ const remainingUtf8Bytes = maxUtf8Bytes - usedUtf8Bytes;
4987
+ const budgeted = limitDocsMcpUtf8Bytes(perPage.text, remainingUtf8Bytes);
4988
+ const document = budgeted.text;
4989
+ const documentBytes = docsMcpUtf8Bytes(document);
4990
+ usedUtf8Bytes += documentBytes;
4991
+ const pageTruncated = perPage.truncated || budgeted.truncated;
4992
+ truncated ||= pageTruncated;
4993
+ results.push({
4994
+ requestedPath,
4995
+ page: toStructuredDocsMcpPage(page),
4996
+ document,
4997
+ chars: document.length,
4998
+ totalChars: fullDocument.length,
4999
+ truncated: pageTruncated
5000
+ });
5001
+ }
5002
+ const result = {
5003
+ format: "docs-read-pages.v1",
5004
+ budget: {
5005
+ requestedTokens: tokenBudget,
5006
+ strategy: "utf8-bytes",
5007
+ maxUtf8Bytes,
5008
+ usedUtf8Bytes,
5009
+ remainingUtf8Bytes: Math.max(0, maxUtf8Bytes - usedUtf8Bytes),
5010
+ truncated: truncated || processedCount < requestedPaths.length
5011
+ },
5012
+ resultCount: results.length,
5013
+ requestedCount: requestedPaths.length,
5014
+ pages: results,
5015
+ errors,
5016
+ remainingPaths: requestedPaths.slice(processedCount)
5017
+ };
5018
+ trackMcpTool("read_pages", {
5019
+ locale,
5020
+ resultCount: results.length
5021
+ });
5022
+ return createStructuredTextResult(result);
5023
+ });
5024
+ if (resolved.tools.submitFeedback !== false && agentFeedback.enabled) registerTool("submit_feedback", {
5025
+ title: "Submit documentation feedback",
5026
+ description: "Submit machine-readable documentation feedback. The payload is validated against the site's configured agent feedback schema before delivery.",
5027
+ inputSchema: submitFeedbackInputSchema,
5028
+ outputSchema: submitFeedbackOutputSchema,
5029
+ annotations: {
5030
+ readOnlyHint: false,
5031
+ destructiveHint: false,
5032
+ idempotentHint: false
5033
+ },
5034
+ _meta: { "dev.farming-labs/feedbackSchema": agentFeedback.schema }
5035
+ }, async ({ context, payload }) => {
5036
+ const validationError = validateDocsAgentFeedbackPayload(payload, agentFeedback.payloadSchema);
5037
+ if (validationError) return {
5038
+ content: [{
5039
+ type: "text",
5040
+ text: validationError
5041
+ }],
5042
+ isError: true
5043
+ };
5044
+ const feedbackContext = {
5045
+ ...context,
5046
+ source: context?.source ?? "mcp"
5047
+ };
5048
+ await agentFeedback.onFeedback?.({
5049
+ ...Object.keys(feedbackContext).length > 0 ? { context: feedbackContext } : {},
5050
+ payload
5051
+ });
5052
+ trackMcpTool("submit_feedback", {
5053
+ locale: feedbackContext.locale,
5054
+ resultCount: 1
5055
+ });
5056
+ return createStructuredTextResult({
5057
+ accepted: true,
5058
+ message: "Feedback accepted."
5059
+ });
5060
+ });
4583
5061
  if (resolved.tools.readPage) registerTool("read_page", {
4584
5062
  title: "Read a docs page",
4585
5063
  description: "Read a documentation page by slug or URL path, optionally selecting one heading and limiting returned characters.",
@@ -4857,9 +5335,10 @@ function createDocsMcpHttpHandler(options) {
4857
5335
  const contentChangePollIntervalMs = typeof configuredPollInterval === "number" && Number.isFinite(configuredPollInterval) && configuredPollInterval >= 10 ? Math.floor(configuredPollInterval) : DEFAULT_DOCS_MCP_CONTENT_CHANGE_POLL_INTERVAL_MS;
4858
5336
  async function readMonitoredState(context) {
4859
5337
  const locale = options.source.resolveLocale?.(void 0, context);
4860
- const pages = dedupePages(await options.source.getPages(locale, context));
5338
+ const pages = dedupePages(filterDocsPagesByAccess(await options.source.getPages(locale, context), context.auth));
4861
5339
  const result = await contentChangeFeed.resolve({
4862
5340
  pages: toSearchSourcePages(pages),
5341
+ principal: context.auth,
4863
5342
  search: options.search,
4864
5343
  audience: "agent",
4865
5344
  locale,
@@ -5018,9 +5497,10 @@ async function runDocsMcpStdio(options) {
5018
5497
  if (!contentChangesEnabled) return server;
5019
5498
  const readState = async () => {
5020
5499
  const locale = options.source.resolveLocale?.(void 0, requestContext);
5021
- const pages = dedupePages(await options.source.getPages(locale, requestContext));
5500
+ const pages = dedupePages(filterDocsPagesByAccess(await options.source.getPages(locale, requestContext), requestContext.auth));
5022
5501
  const result = await contentChangeFeed.resolve({
5023
5502
  pages: toSearchSourcePages(pages),
5503
+ principal: requestContext.auth,
5024
5504
  search: options.search,
5025
5505
  audience: "agent",
5026
5506
  locale,
@@ -5440,6 +5920,7 @@ function scanFilesystemDocsPages(contentDirAbs, entry, rootDir) {
5440
5920
  description: data.description,
5441
5921
  relatedInput: data.related,
5442
5922
  agent: normalizePageAgentFrontmatter(data.agent),
5923
+ okf: normalizeDocsOkfTrustMetadataInput(data.okf),
5443
5924
  icon: data.icon,
5444
5925
  sourcePath: path.relative(rootDir, full).replace(/\\/g, "/"),
5445
5926
  lastmod: normalizeFrontmatterLastmod(data.lastmod),
@@ -6270,6 +6751,7 @@ async function buildDocsMcpContext(options) {
6270
6751
  const orderedResults = [...await performDocsSearch({
6271
6752
  pages: scopedPages.map((page) => searchPageBySource.get(page)),
6272
6753
  generationPages: allSearchPages,
6754
+ principal: options.principal,
6273
6755
  query: options.query,
6274
6756
  search: {
6275
6757
  enabled: true,