@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.
- package/dist/{agent-BLHMoVId.mjs → agent-CaCWThBY.mjs} +4 -4
- package/dist/{agent-CFqECAG0.mjs → agent-DCTGj6J7.mjs} +212 -19
- package/dist/{agent-evals-GpcaCbRb.mjs → agent-evals-D-sPJfXz.mjs} +5 -5
- package/dist/{agent-export-oiWxB5aX.mjs → agent-export-GcTZPLfB.mjs} +30 -17
- package/dist/agent-feedback-evaluations-DQCTFegz.mjs +240 -0
- package/dist/agent-feedback-loop-vaC-M1sx.mjs +196 -0
- package/dist/agent-maintenance-CuZ7iAKq.mjs +289 -0
- package/dist/agent-skills-bundle.d.mts +1 -1
- package/dist/{agent-skills-progressive-disclosure-Da9LKEKv.mjs → agent-skills-progressive-disclosure-DL-q4JZo.mjs} +1 -1
- package/dist/{agent-skills-server-CICMh0zv.d.mts → agent-skills-server-D34054x5.d.mts} +2 -2
- package/dist/{agent-skills-server-DFqQyO_V.mjs → agent-skills-server-Ddbf15pY.mjs} +1 -1
- package/dist/agent-skills-vite.d.mts +3 -3
- package/dist/agent-skills-vite.mjs +2 -2
- package/dist/{agents-CtqIUW5I.mjs → agents-e1SYT64z.mjs} +11 -10
- package/dist/{analytics-Bx44lg6d.mjs → analytics-CqfcIBdg.mjs} +1 -1
- package/dist/{analytics-C75BnTEW.d.mts → analytics-aeXwEvQX.d.mts} +1 -1
- package/dist/authoring-Do87QzBd.mjs +41 -0
- package/dist/authoring-mcp-Be7quE5R.mjs +380 -0
- package/dist/browser.d.mts +3 -3
- package/dist/browser.mjs +2 -2
- package/dist/cli/index.mjs +103 -35
- package/dist/client/react.d.mts +1 -1
- package/dist/client/react.mjs +1 -1
- package/dist/{cloud-FNfcbfli.mjs → cloud-Bvof6mET.mjs} +3 -3
- package/dist/{cloud-ask-ai-BmbZVAT5.d.mts → cloud-ask-ai-BqfiQaNZ.d.mts} +1 -1
- package/dist/{cloud-ask-ai-CkCYjf2N.mjs → cloud-ask-ai-CK2qlNns.mjs} +1 -1
- package/dist/{code-blocks-Bel6vDqI.mjs → code-blocks-D90CCJFs.mjs} +1 -1
- package/dist/{codeblocks-jbWuvtJQ.mjs → codeblocks-C911CdyJ.mjs} +2 -2
- package/dist/{agent-lWTq6pgr.d.mts → content-change-hydration-CoYRs3pC.d.mts} +707 -675
- package/dist/{dev-BvbYmTLz.mjs → dev-Bx0JNglW.mjs} +3 -3
- package/dist/docs-cloud-server.d.mts +2 -2
- package/dist/docs-cloud-server.mjs +2 -2
- package/dist/{doctor-Bmm0uY98.mjs → doctor-CtK1CRPj.mjs} +20 -16
- package/dist/{downgrade-Dqm0rJMy.mjs → downgrade-BXmQMkMy.mjs} +2 -2
- package/dist/feedback-DWVUMrxy.mjs +72 -0
- package/dist/feedback-evals-k0sFDdms.mjs +146 -0
- package/dist/{golden-evaluations-K45fcWGW.mjs → golden-evaluations-De4KwoYr.mjs} +3 -3
- package/dist/index.d.mts +236 -9
- package/dist/index.mjs +14 -11
- package/dist/{init-jDQSo-Or.mjs → init-CCV2eRNB.mjs} +3 -3
- package/dist/markdown-rendering.mjs +1 -1
- package/dist/{markdown-sections-7OoA7ylx.mjs → markdown-sections-BKy4labo.mjs} +110 -2
- package/dist/{mcp-CwgKlbIz.mjs → mcp-DsjWxF7g.mjs} +11 -10
- package/dist/mcp.d.mts +20 -5
- package/dist/mcp.mjs +538 -56
- package/dist/{metadata-D4v_R1t5.mjs → metadata-7htr-Xj_.mjs} +1 -1
- package/dist/{content-change-hydration-BsbBXAUi.mjs → openapi-mcp-DQ3xptKo.mjs} +182 -4
- package/dist/openapi-mcp-node-vO3IXVGI.mjs +43 -0
- package/dist/{package-version--oQiHwZj.mjs → package-version-Ci99L0yo.mjs} +1 -1
- package/dist/{prompt-references-DW5vG5zs.mjs → prompt-references-1Wce71c7.mjs} +3 -3
- package/dist/propose-DJOiHu9Y.mjs +73 -0
- package/dist/retrieval-digest-B-SgtXKK.mjs +177 -0
- package/dist/retrieval-digest-DyNRD-mt.d.mts +467 -0
- package/dist/{review-C_5wB0rR.mjs → review-XI9FXeos.mjs} +12 -12
- package/dist/{robots-NQesPX5L.mjs → robots-BOjWB1ky.mjs} +2 -2
- package/dist/{robots-BQ42Y84a.mjs → robots-CzKesbdX.mjs} +6 -6
- package/dist/{search-B5etKU-v.mjs → search-DwAoC9X7.mjs} +12 -11
- package/dist/server.d.mts +47 -264
- package/dist/server.mjs +14 -11
- package/dist/{sidebar-CwZlgyAz.d.mts → sidebar-CJze8SBi.d.mts} +1 -1
- package/dist/{sitemap-DjhxvQP1.mjs → sitemap-DOaJIKJf.mjs} +11 -10
- package/dist/{sitemap-server-B0SiREN0.mjs → sitemap-server-NeVu8W38.mjs} +11 -1
- package/dist/{skills-BFang8vF.mjs → skills-BILSESek.mjs} +11 -10
- package/dist/{standards-discovery-Ckx0tN7B.mjs → standards-discovery-Bpz90VYq.mjs} +2 -177
- package/dist/{standards-discovery-D1Ys4J3h.d.mts → standards-discovery-Xm84bS4M.d.mts} +1 -1
- package/dist/{types-DLLefoU5.d.mts → types-QhYMnKvb.d.mts} +148 -2
- package/dist/{upgrade-kj2crJSk.mjs → upgrade-B92U83xo.mjs} +2 -2
- package/package.json +1 -1
- package/dist/retrieval-digest-DYEsIeCE.d.mts +0 -164
- /package/dist/{audience-exposure-Cw3SA4WE.d.mts → audience-exposure-CkNZNeVX.d.mts} +0 -0
- /package/dist/{cloud-analytics-CSyFE6SS.mjs → cloud-analytics-Ci1yiwD8.mjs} +0 -0
- /package/dist/{config-BaTmjDqD.mjs → config-DrZ3fXgf.mjs} +0 -0
- /package/dist/{errors-DZiNXv5c.mjs → errors-BDmra6mz.mjs} +0 -0
- /package/dist/{i18n-B3BIxShC.mjs → i18n-B1gJ9EW0.mjs} +0 -0
- /package/dist/{sidebar-IcpElOVH.mjs → sidebar-3W51s2sF.mjs} +0 -0
- /package/dist/{templates-kc7jxspR.mjs → templates-DdB4BBGq.mjs} +0 -0
- /package/dist/{utils-DOMeZW9V.mjs → utils-Cc1SrVza.mjs} +0 -0
package/dist/mcp.mjs
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import { n as
|
|
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
|
|
1451
|
+
description: "Copy, Open in LLM, MCP connection, and Agent Skills setup actions.",
|
|
1384
1452
|
docs: "/docs/customization/page-actions",
|
|
1385
|
-
children: [
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
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
|
|
3342
|
+
async function getSourcePages(locale) {
|
|
3343
|
+
return getResolvedSourcePages(resolveSourceLocale(locale));
|
|
3127
3344
|
}
|
|
3128
|
-
function
|
|
3129
|
-
return options.source.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|