@farming-labs/docs 0.2.75 → 0.2.78

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 (39) hide show
  1. package/dist/{agent-BHqaZx-X.mjs → agent-Ba94vwM2.mjs} +4308 -340
  2. package/dist/{agent-cAPjC2jb.d.mts → agent-CHgWJcK_.d.mts} +60 -2
  3. package/dist/{agent-DxTa3PLB.mjs → agent-CM-hzhfh.mjs} +2 -2
  4. package/dist/{agent-evals-21LZ_uEl.mjs → agent-evals-C8dkgJMN.mjs} +1 -1
  5. package/dist/{agent-export-3VheegEK.mjs → agent-export-DdF-IVfg.mjs} +8 -7
  6. package/dist/agent-provenance-D8UBpnMc.mjs +463 -0
  7. package/dist/agent-skills-bundle.d.mts +1 -1
  8. package/dist/{agent-skills-server-noFn1l_p.mjs → agent-skills-server-B2EXn2QD.mjs} +1 -1
  9. package/dist/{agent-skills-server-Cix64aGB.d.mts → agent-skills-server-zvs0Yknt.d.mts} +2 -2
  10. package/dist/agent-skills-vite.d.mts +3 -3
  11. package/dist/agent-skills-vite.mjs +2 -2
  12. package/dist/{agents-ByTdP0Hl.mjs → agents-v0Em2KZo.mjs} +7 -16
  13. package/dist/cli/index.mjs +15 -15
  14. package/dist/client/react.d.mts +1 -1
  15. package/dist/{cloud-ask-ai-Bt0rzFbe.d.mts → cloud-ask-ai-BwsdF7AQ.d.mts} +1 -1
  16. package/dist/{dev-Cc1cFFj-.mjs → dev-kjaxOOla.mjs} +8 -2
  17. package/dist/docs-cloud-server.d.mts +2 -2
  18. package/dist/{doctor-C34VVF85.mjs → doctor-BbrN0rdC.mjs} +56 -24
  19. package/dist/{golden-evaluations-D_oDJwGv.mjs → golden-evaluations-Bk9t-HNq.mjs} +12 -3
  20. package/dist/index.d.mts +5 -5
  21. package/dist/index.mjs +6 -6
  22. package/dist/{mcp-Bnqfu8P5.mjs → mcp-DT-WNsA-.mjs} +6 -6
  23. package/dist/mcp.d.mts +28 -6
  24. package/dist/mcp.mjs +557 -62
  25. package/dist/{prompt-references-CfyYOa_W.mjs → prompt-references-CoDorsvS.mjs} +3 -3
  26. package/dist/{retrieval-digest-CFOcSKTo.d.mts → retrieval-digest-CO3wqeMk.d.mts} +105 -6
  27. package/dist/{review-Dw6oCQX3.mjs → review-DrCaaT7G.mjs} +5 -5
  28. package/dist/{robots-DaYBoy_A.mjs → robots-C_v2HK43.mjs} +2 -2
  29. package/dist/{robots-KWLujSLI.mjs → robots-izJbM9cv.mjs} +3 -3
  30. package/dist/{search-DBdmbUkc.mjs → search-YiZMZDfb.mjs} +6 -6
  31. package/dist/server.d.mts +9 -7
  32. package/dist/server.mjs +7 -7
  33. package/dist/{sitemap-CrzG5Wpf.mjs → sitemap-NO8UMO6Y.mjs} +6 -6
  34. package/dist/{sitemap-server-B9LZQ1FE.mjs → sitemap-server-Dgxs9V6S.mjs} +12 -3
  35. package/dist/{standards-discovery-BwL5ffGx.mjs → standards-discovery-BKlEnK_H.mjs} +32 -4
  36. package/dist/{standards-discovery-DBI1PD7m.d.mts → standards-discovery-BWyNgZSs.d.mts} +34 -7
  37. package/dist/{types-BDRwrExu.d.mts → types-CpLnwHak.d.mts} +190 -2
  38. package/package.json +1 -1
  39. package/dist/search-BmC8wVr0.mjs +0 -3490
package/dist/mcp.mjs CHANGED
@@ -1,15 +1,15 @@
1
1
  import { a as emitDocsAnalyticsEvent, i as emitDocsAgentTraceEvent, n as createDocsAgentTraceContext, r as createDocsAgentTraceId } from "./analytics-Bx44lg6d.mjs";
2
- import { E as DOCS_MARKDOWN_SECTION_INDEX_FORMAT, Kt as normalizeDocsRelated, P as buildDocsMarkdownSectionIndex, an as isDocsMcpResourcePath, cn as resolveDocsMcpProtectedResourceMetadataLocation, jt as resolveDocsAudienceMdxContent, ln as resolveDocsMcpResourceLocation, nn as isDocsMcpOAuthScopeToken, on as normalizeDocsMcpAuthorizationServerUrls, qt as renderDocsRelatedMarkdownLines, rn as isDocsMcpProtectedResourceMetadataPath, sn as normalizeDocsMcpEndpointPath, wt as toDocsMarkdownUrl } from "./agent-BHqaZx-X.mjs";
2
+ import { $n as resolveDocsMcpResourceLocation, An as normalizeAgentScopeValues, Dn as agentVersionConstraintsOverlap, E as DOCS_MARKDOWN_SECTION_INDEX_FORMAT, En as agentVersionConstraintMatches, Fn as isDocsRetrievalCanonicalUrl, In as normalizeDocsRelated, Kn as isDocsMcpOAuthScopeToken, Ln as renderDocsRelatedMarkdownLines, Mn as DocsPaginationCursorError, Nn as paginateDocsItems, On as normalizeAgentFramework, P as buildDocsMarkdownSectionIndex, Qn as resolveDocsMcpProtectedResourceMetadataLocation, Xn as normalizeDocsMcpAuthorizationServerUrls, Yn as isDocsMcpResourcePath, Zn as normalizeDocsMcpEndpointPath, dn as performDocsSearchWithMetadata, en as buildDocsSearchFacets, kn as normalizeAgentLocale, ln as normalizeDocsSearchFilters, qn as isDocsMcpProtectedResourceMetadataPath, un as performDocsSearch, wn as resolveDocsAudienceMdxContent, wt as toDocsMarkdownUrl } from "./agent-Ba94vwM2.mjs";
3
3
  import { S as upsertPageAgentContractMarkdown, _ as hasStructuredPageAgentContract, o as findDocsMarkdownSection, s as parseDocsMarkdownSections, v as normalizePageAgentFrontmatter } from "./markdown-sections-7OoA7ylx.mjs";
4
- import "./standards-discovery-BwL5ffGx.mjs";
5
- import { C as agentVersionConstraintsOverlap, E as normalizeAgentScopeValues, I as stripGeneratedAgentProvenance, R as resolvePageSidebarFolderIndexBehavior, S as agentVersionConstraintMatches, T as normalizeAgentLocale, U as emitDocsTelemetryMcpToolEvent, V as emitDocsTelemetryAgentSurfaceEvent, W as emitDocsTelemetryProjectEvent, f as normalizeDocsSearchFilters, k as isDocsRetrievalCanonicalUrl, m as performDocsSearchWithMetadata, p as performDocsSearch, w as normalizeAgentFramework } from "./search-BmC8wVr0.mjs";
4
+ import "./standards-discovery-BKlEnK_H.mjs";
5
+ import { f as emitDocsTelemetryAgentSurfaceEvent, h as emitDocsTelemetryProjectEvent, l as resolvePageSidebarFolderIndexBehavior, m as emitDocsTelemetryMcpToolEvent, s as stripGeneratedAgentProvenance } from "./agent-provenance-D8UBpnMc.mjs";
6
6
  import matter from "gray-matter";
7
7
  import fs from "node:fs";
8
8
  import path from "node:path";
9
9
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10
10
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
11
11
  import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
12
- import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
12
+ import { ErrorCode, ListPromptsRequestSchema, ListResourceTemplatesRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, McpError, isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
13
13
  import * as z from "zod/v4";
14
14
 
15
15
  //#region src/mcp.ts
@@ -18,6 +18,9 @@ const DEFAULT_MCP_NAME = "@farming-labs/docs";
18
18
  const DEFAULT_MCP_CONTEXT_TOKEN_BUDGET = 4e3;
19
19
  const MIN_MCP_CONTEXT_TOKEN_BUDGET = 256;
20
20
  const MAX_MCP_CONTEXT_TOKEN_BUDGET = 32e3;
21
+ const DOCS_MCP_PROTOCOL_LIST_PAGE_SIZE = 10;
22
+ const DOCS_MCP_TOOL_LIST_PAGE_SIZE = 25;
23
+ const DOCS_MCP_PAGINATION_META_KEY = "dev.farming-labs/pagination";
21
24
  const UTF8_ENCODER = new TextEncoder();
22
25
  const DEFAULT_DOCS_MCP_MAX_BODY_BYTES = 1024 * 1024;
23
26
  const DEFAULT_DOCS_MCP_CORS_MAX_AGE_SECONDS = 600;
@@ -220,9 +223,44 @@ const DOCS_CONFIG_SCHEMA_OPTIONS_TEMPLATE = [
220
223
  path: "agent",
221
224
  name: "agent",
222
225
  type: "DocsAgentConfig",
223
- description: "Agent compaction defaults and offline-by-default usefulness evaluations.",
226
+ description: "Agent synchronization, reusable skills, compaction defaults, and offline-by-default usefulness evaluations.",
224
227
  docs: "/docs/getting-started/agent-ready-docs",
225
228
  children: [
229
+ {
230
+ path: "agent.contentChanges",
231
+ name: "contentChanges",
232
+ type: "boolean | DocsAgentContentChangesConfig",
233
+ default: true,
234
+ description: "Body-free runtime document synchronization feed. Disable it with false, or configure bounded and durable snapshot history.",
235
+ children: [
236
+ {
237
+ path: "agent.contentChanges.enabled",
238
+ name: "enabled",
239
+ type: "boolean",
240
+ default: true,
241
+ description: "Whether runtime adapters serve response=changes."
242
+ },
243
+ {
244
+ path: "agent.contentChanges.maxSnapshots",
245
+ name: "maxSnapshots",
246
+ type: "number",
247
+ default: 8,
248
+ description: "Metadata-only snapshots retained in this server process; accepted range is 1 through 64."
249
+ },
250
+ {
251
+ path: "agent.contentChanges.loadSnapshot",
252
+ name: "loadSnapshot",
253
+ type: "DocsContentChangeSnapshotLoader",
254
+ description: "Optional callback that loads a prior generation from durable storage for exact cross-deployment deltas."
255
+ },
256
+ {
257
+ path: "agent.contentChanges.saveSnapshot",
258
+ name: "saveSnapshot",
259
+ type: "DocsContentChangeSnapshotSaver",
260
+ description: "Optional callback that persists each current metadata snapshot to durable storage."
261
+ }
262
+ ]
263
+ },
226
264
  {
227
265
  path: "agent.skills",
228
266
  name: "skills",
@@ -1523,6 +1561,13 @@ const DOCS_CONFIG_SCHEMA_OPTIONS_TEMPLATE = [
1523
1561
  default: true,
1524
1562
  description: "Expose the search_docs tool."
1525
1563
  },
1564
+ {
1565
+ path: "mcp.tools.searchFacets",
1566
+ name: "searchFacets",
1567
+ type: "boolean",
1568
+ default: true,
1569
+ description: "Expose the list_search_facets tool for body-free framework, version, package, and tag discovery."
1570
+ },
1526
1571
  {
1527
1572
  path: "mcp.tools.readPage",
1528
1573
  name: "readPage",
@@ -1562,17 +1607,26 @@ const DOCS_CONFIG_SCHEMA_OPTIONS_TEMPLATE = [
1562
1607
  default: false,
1563
1608
  description: "Generated API reference pages from framework route conventions or a hosted OpenAPI document.",
1564
1609
  docs: "/docs/customization/api-reference",
1565
- children: [{
1566
- path: "apiReference.specUrl",
1567
- name: "specUrl",
1568
- type: "string",
1569
- description: "Remote OpenAPI JSON URL when the backend owns the schema."
1570
- }, {
1571
- path: "apiReference.path",
1572
- name: "path",
1573
- type: "string",
1574
- description: "Docs route where the API reference is rendered."
1575
- }]
1610
+ children: [
1611
+ {
1612
+ path: "apiReference.specUrl",
1613
+ name: "specUrl",
1614
+ type: "string",
1615
+ description: "Remote OpenAPI JSON URL when the backend owns the schema."
1616
+ },
1617
+ {
1618
+ path: "apiReference.path",
1619
+ name: "path",
1620
+ type: "string",
1621
+ description: "Docs route where the API reference is rendered."
1622
+ },
1623
+ {
1624
+ path: "apiReference.catalogTargets",
1625
+ name: "catalogTargets",
1626
+ type: "string[]",
1627
+ description: "Product API base URLs that the OpenAPI document describes in the RFC 9727 catalog."
1628
+ }
1629
+ ]
1576
1630
  },
1577
1631
  {
1578
1632
  path: "codeBlocks",
@@ -1704,9 +1758,11 @@ export default defineDocs({
1704
1758
  }
1705
1759
  ]);
1706
1760
  const searchFilterValueSchema = z.union([z.string().trim().min(1).max(128), z.array(z.string().trim().min(1).max(128)).min(1).max(16)]);
1761
+ const paginationCursorInputSchema = z.string().min(1).max(4096).describe("Opaque nextCursor from the preceding response page.").optional();
1707
1762
  const searchDocsInputSchema = z.object({
1708
1763
  query: z.string().trim().min(1),
1709
1764
  limit: z.number().int().min(1).max(25).optional(),
1765
+ cursor: paginationCursorInputSchema,
1710
1766
  locale: z.string().trim().min(1).max(128).optional(),
1711
1767
  audience: z.enum(["human", "agent"]).optional(),
1712
1768
  framework: searchFilterValueSchema.optional(),
@@ -1714,6 +1770,22 @@ const searchDocsInputSchema = z.object({
1714
1770
  package: searchFilterValueSchema.optional(),
1715
1771
  tags: searchFilterValueSchema.optional()
1716
1772
  });
1773
+ const searchFacetsInputSchema = z.object({
1774
+ locale: z.string().trim().min(1).max(128).optional(),
1775
+ audience: z.enum(["human", "agent"]).optional(),
1776
+ facet: z.enum([
1777
+ "framework",
1778
+ "version",
1779
+ "package",
1780
+ "tags"
1781
+ ]).optional(),
1782
+ limit: z.number().int().min(1).max(100).optional(),
1783
+ cursor: paginationCursorInputSchema,
1784
+ framework: searchFilterValueSchema.optional(),
1785
+ version: searchFilterValueSchema.optional(),
1786
+ package: searchFilterValueSchema.optional(),
1787
+ tags: searchFilterValueSchema.optional()
1788
+ });
1717
1789
  const readPageInputSchema = z.object({
1718
1790
  path: z.string().min(1),
1719
1791
  locale: z.string().trim().min(1).max(128).optional(),
@@ -1724,14 +1796,16 @@ const listPageSectionsInputSchema = z.object({
1724
1796
  path: z.string().min(1),
1725
1797
  locale: z.string().trim().min(1).max(128).optional(),
1726
1798
  tokenBudget: z.number().int().min(1).max(1e6).optional(),
1727
- byteBudget: z.number().int().min(1).max(1e6).optional()
1799
+ byteBudget: z.number().int().min(1).max(1e6).optional(),
1800
+ cursor: paginationCursorInputSchema
1728
1801
  });
1729
1802
  const listTasksInputSchema = z.object({
1730
1803
  query: z.string().trim().min(1).optional(),
1731
1804
  framework: z.string().trim().min(1).optional(),
1732
1805
  version: z.string().trim().min(1).optional(),
1733
1806
  package: z.string().trim().min(1).optional(),
1734
- locale: z.string().trim().min(1).max(128).optional()
1807
+ locale: z.string().trim().min(1).max(128).optional(),
1808
+ cursor: paginationCursorInputSchema
1735
1809
  });
1736
1810
  const readTaskInputSchema = readPageInputSchema;
1737
1811
  const pageAgentAppliesToOutputSchema = z.object({
@@ -1775,8 +1849,14 @@ const taskSummaryOutputSchema = z.object({
1775
1849
  outcome: z.string().optional(),
1776
1850
  appliesTo: pageAgentAppliesToOutputSchema.optional()
1777
1851
  });
1852
+ const paginationOutputShape = {
1853
+ resultCount: z.number().int().nonnegative().describe("Items returned in this response page."),
1854
+ total: z.number().int().nonnegative().describe("Items in the complete filtered result set."),
1855
+ hasMore: z.boolean().describe("Whether another response page is available."),
1856
+ nextCursor: z.string().describe("Opaque cursor to pass as cursor on the next request.").optional()
1857
+ };
1778
1858
  const listTasksOutputSchema = z.object({
1779
- resultCount: z.number().int().nonnegative(),
1859
+ ...paginationOutputShape,
1780
1860
  tasks: z.array(taskSummaryOutputSchema)
1781
1861
  });
1782
1862
  const readTaskOutputSchema = z.object({
@@ -1790,10 +1870,14 @@ const readTaskOutputSchema = z.object({
1790
1870
  }),
1791
1871
  contract: pageAgentContractOutputSchema
1792
1872
  });
1793
- const listPagesInputSchema = z.object({ locale: z.string().trim().min(1).max(128).optional() });
1873
+ const listPagesInputSchema = z.object({
1874
+ locale: z.string().trim().min(1).max(128).optional(),
1875
+ cursor: paginationCursorInputSchema
1876
+ });
1794
1877
  const listDocsInputSchema = z.object({
1795
1878
  section: z.string().trim().min(1).optional(),
1796
- locale: z.string().trim().min(1).max(128).optional()
1879
+ locale: z.string().trim().min(1).max(128).optional(),
1880
+ cursor: paginationCursorInputSchema
1797
1881
  });
1798
1882
  const getNavigationInputSchema = z.object({ locale: z.string().trim().min(1).max(128).optional() });
1799
1883
  const getConfigSchemaInputSchema = z.object({
@@ -1846,10 +1930,13 @@ const docsSectionOutputSchema = z.lazy(() => z.object({
1846
1930
  pages: z.array(pageSummaryOutputSchema),
1847
1931
  sections: z.array(docsSectionOutputSchema)
1848
1932
  }));
1849
- const listPagesOutputSchema = z.object({ pages: z.array(pageSummaryOutputSchema) });
1933
+ const listPagesOutputSchema = z.object({
1934
+ ...paginationOutputShape,
1935
+ pages: z.array(pageSummaryOutputSchema)
1936
+ });
1850
1937
  const listDocsOutputSchema = z.object({
1938
+ ...paginationOutputShape,
1851
1939
  section: z.string().optional(),
1852
- resultCount: z.number().int().nonnegative(),
1853
1940
  sectionCount: z.number().int().nonnegative(),
1854
1941
  pages: z.array(pageSummaryOutputSchema),
1855
1942
  rootPages: z.array(pageSummaryOutputSchema),
@@ -1951,10 +2038,34 @@ const searchDocsOutputSchema = z.object({
1951
2038
  audience: z.enum(["human", "agent"]),
1952
2039
  filters: searchFiltersOutputSchema,
1953
2040
  indexGeneration: retrievalSourceDigestOutputSchema,
1954
- resultCount: z.number().int().nonnegative(),
2041
+ ...paginationOutputShape,
1955
2042
  results: z.array(searchResultOutputSchema),
1956
2043
  warnings: z.array(searchWarningOutputSchema)
1957
2044
  });
2045
+ const searchFacetOutputSchema = z.object({
2046
+ valueCount: z.number().int().nonnegative(),
2047
+ total: z.number().int().nonnegative(),
2048
+ hasMore: z.boolean(),
2049
+ nextCursor: z.string().optional(),
2050
+ truncated: z.boolean(),
2051
+ values: z.array(z.object({
2052
+ value: z.string(),
2053
+ count: z.number().int().nonnegative()
2054
+ }))
2055
+ });
2056
+ const searchFacetsOutputSchema = z.object({
2057
+ format: z.literal("docs-search-facets.v1"),
2058
+ audience: z.enum(["human", "agent"]),
2059
+ filters: searchFiltersOutputSchema,
2060
+ indexGeneration: retrievalSourceDigestOutputSchema,
2061
+ matchedPageCount: z.number().int().nonnegative(),
2062
+ facets: z.object({
2063
+ framework: searchFacetOutputSchema,
2064
+ version: searchFacetOutputSchema,
2065
+ package: searchFacetOutputSchema,
2066
+ tags: searchFacetOutputSchema
2067
+ })
2068
+ });
1958
2069
  const codeExampleOutputSchema = z.object({
1959
2070
  id: z.string(),
1960
2071
  page: z.object({
@@ -2046,6 +2157,7 @@ const pageSectionIndexOutputSchema = z.object({
2046
2157
  sectionIndexUrl: z.string(),
2047
2158
  lineNumbering: z.literal("body"),
2048
2159
  sectionCount: z.number().int().nonnegative(),
2160
+ ...paginationOutputShape,
2049
2161
  estimatedTokens: z.number().int().nonnegative(),
2050
2162
  utf8Bytes: z.number().int().nonnegative(),
2051
2163
  fetchBudget: z.object({
@@ -2115,6 +2227,7 @@ function resolveDocsMcpConfig(mcp, defaults = {}) {
2115
2227
  listTasks: true,
2116
2228
  readTask: true,
2117
2229
  searchDocs: true,
2230
+ searchFacets: true,
2118
2231
  getNavigation: true,
2119
2232
  getCodeExamples: true,
2120
2233
  getConfigSchema: true,
@@ -2136,6 +2249,7 @@ function resolveDocsMcpConfig(mcp, defaults = {}) {
2136
2249
  listTasks: config.tools?.listTasks ?? true,
2137
2250
  readTask: config.tools?.readTask ?? true,
2138
2251
  searchDocs: config.tools?.searchDocs ?? true,
2252
+ searchFacets: config.tools?.searchFacets ?? true,
2139
2253
  getNavigation: config.tools?.getNavigation ?? true,
2140
2254
  getCodeExamples: config.tools?.getCodeExamples ?? true,
2141
2255
  getConfigSchema: config.tools?.getConfigSchema ?? true,
@@ -2263,16 +2377,155 @@ function createStructuredTextResult(structuredContent, text) {
2263
2377
  structuredContent
2264
2378
  };
2265
2379
  }
2380
+ function stableDocsMcpPaginationScope(values) {
2381
+ return JSON.stringify(values);
2382
+ }
2383
+ function docsMcpPaginationSnapshot(values) {
2384
+ return JSON.stringify(values);
2385
+ }
2386
+ function paginateDocsMcpItems(items, options) {
2387
+ return paginateDocsItems(items, {
2388
+ kind: options.kind,
2389
+ scope: options.scope,
2390
+ snapshot: docsMcpPaginationSnapshot(items),
2391
+ cursor: options.cursor,
2392
+ pageSize: options.pageSize ?? DOCS_MCP_TOOL_LIST_PAGE_SIZE
2393
+ });
2394
+ }
2395
+ function paginateDocsMcpProtocolItems(items, options) {
2396
+ try {
2397
+ return paginateDocsMcpItems(items, {
2398
+ ...options,
2399
+ pageSize: DOCS_MCP_PROTOCOL_LIST_PAGE_SIZE
2400
+ });
2401
+ } catch (error) {
2402
+ if (error instanceof DocsPaginationCursorError) throw new McpError(ErrorCode.InvalidParams, error.message);
2403
+ throw error;
2404
+ }
2405
+ }
2406
+ function toDocsMcpProtocolPaginationMeta(result) {
2407
+ return { [DOCS_MCP_PAGINATION_META_KEY]: {
2408
+ hasMore: result.hasMore,
2409
+ total: result.total
2410
+ } };
2411
+ }
2412
+ function paginateDocsMcpSdkListResult(result, field, options) {
2413
+ const page = paginateDocsMcpProtocolItems([...result[field]].sort(options.compare), options);
2414
+ return {
2415
+ ...result,
2416
+ [field]: page.items,
2417
+ nextCursor: page.nextCursor,
2418
+ _meta: {
2419
+ ...result._meta,
2420
+ ...toDocsMcpProtocolPaginationMeta(page)
2421
+ }
2422
+ };
2423
+ }
2424
+ /**
2425
+ * The MCP SDK owns the live registries and descriptor serialization. Intercepting the
2426
+ * handlers it installs lets pagination operate on that live output without copying private
2427
+ * registry state or drifting from SDK behavior when callers register, update, or disable
2428
+ * tools/resources after this factory returns.
2429
+ */
2430
+ function installDocsMcpSdkListPaginationInterceptor(server, scope) {
2431
+ const originalSetRequestHandler = server.server.setRequestHandler;
2432
+ const setRequestHandler = originalSetRequestHandler.bind(server.server);
2433
+ const interceptedSetRequestHandler = ((requestSchema, handler) => {
2434
+ const sdkHandler = handler;
2435
+ if (Object.is(requestSchema, ListResourcesRequestSchema)) {
2436
+ setRequestHandler(ListResourcesRequestSchema, async (request, extra) => {
2437
+ return paginateDocsMcpSdkListResult(await sdkHandler(request, extra), "resources", {
2438
+ kind: "mcp.protocol/resources-list",
2439
+ scope,
2440
+ cursor: request.params?.cursor,
2441
+ compare: (left, right) => {
2442
+ const uriOrder = left.uri.localeCompare(right.uri);
2443
+ return uriOrder !== 0 ? uriOrder : left.name.localeCompare(right.name);
2444
+ }
2445
+ });
2446
+ });
2447
+ return;
2448
+ }
2449
+ if (Object.is(requestSchema, ListResourceTemplatesRequestSchema)) {
2450
+ setRequestHandler(ListResourceTemplatesRequestSchema, async (request, extra) => {
2451
+ return paginateDocsMcpSdkListResult(await sdkHandler(request, extra), "resourceTemplates", {
2452
+ kind: "mcp.protocol/resource-templates-list",
2453
+ scope,
2454
+ cursor: request.params?.cursor,
2455
+ compare: (left, right) => {
2456
+ const uriOrder = left.uriTemplate.localeCompare(right.uriTemplate);
2457
+ return uriOrder !== 0 ? uriOrder : left.name.localeCompare(right.name);
2458
+ }
2459
+ });
2460
+ });
2461
+ return;
2462
+ }
2463
+ if (Object.is(requestSchema, ListToolsRequestSchema)) {
2464
+ setRequestHandler(ListToolsRequestSchema, async (request, extra) => {
2465
+ return paginateDocsMcpSdkListResult(await sdkHandler(request, extra), "tools", {
2466
+ kind: "mcp.protocol/tools-list",
2467
+ scope,
2468
+ cursor: request.params?.cursor,
2469
+ compare: (left, right) => left.name.localeCompare(right.name)
2470
+ });
2471
+ });
2472
+ return;
2473
+ }
2474
+ if (Object.is(requestSchema, ListPromptsRequestSchema)) {
2475
+ setRequestHandler(ListPromptsRequestSchema, async (request, extra) => {
2476
+ return paginateDocsMcpSdkListResult(await sdkHandler(request, extra), "prompts", {
2477
+ kind: "mcp.protocol/prompts-list",
2478
+ scope,
2479
+ cursor: request.params?.cursor,
2480
+ compare: (left, right) => left.name.localeCompare(right.name)
2481
+ });
2482
+ });
2483
+ return;
2484
+ }
2485
+ setRequestHandler(requestSchema, handler);
2486
+ });
2487
+ server.server.setRequestHandler = interceptedSetRequestHandler;
2488
+ return () => {
2489
+ if (server.server.setRequestHandler === interceptedSetRequestHandler) server.server.setRequestHandler = originalSetRequestHandler;
2490
+ };
2491
+ }
2492
+ function installDocsMcpSdkRegistrationPagination(server, scope) {
2493
+ const wrapRegistration = (registration) => new Proxy(registration, { apply(target, thisArg, argArray) {
2494
+ const restore = installDocsMcpSdkListPaginationInterceptor(server, scope);
2495
+ try {
2496
+ return Reflect.apply(target, thisArg, argArray);
2497
+ } finally {
2498
+ restore();
2499
+ }
2500
+ } });
2501
+ server.registerTool = wrapRegistration(server.registerTool);
2502
+ server.tool = wrapRegistration(server.tool);
2503
+ server.registerResource = wrapRegistration(server.registerResource);
2504
+ server.resource = wrapRegistration(server.resource);
2505
+ server.registerPrompt = wrapRegistration(server.registerPrompt);
2506
+ server.prompt = wrapRegistration(server.prompt);
2507
+ }
2266
2508
  async function createDocsMcpServer(options) {
2267
2509
  const resolved = resolveDocsMcpConfig(options.mcp, {
2268
2510
  defaultName: options.defaultName ?? options.source.siteTitle ?? DEFAULT_MCP_NAME,
2269
2511
  defaultVersion: options.defaultVersion
2270
2512
  });
2271
2513
  const toolSearchConfig = resolveMcpToolSearchConfig(options.search, resolved.route);
2514
+ const protocolScope = stableDocsMcpPaginationScope({
2515
+ name: resolved.name,
2516
+ version: resolved.version
2517
+ });
2272
2518
  const server = new McpServer({
2273
2519
  name: resolved.name,
2274
2520
  version: resolved.version
2275
2521
  });
2522
+ installDocsMcpSdkRegistrationPagination(server, protocolScope);
2523
+ const registerResource = (name, uri, metadata, callback) => {
2524
+ return server.registerResource(name, uri, metadata, callback);
2525
+ };
2526
+ const registerTool = (name, config, callback) => {
2527
+ return server.registerTool(name, config, callback);
2528
+ };
2276
2529
  const telemetryConfig = {
2277
2530
  telemetry: options.telemetry,
2278
2531
  mcp: options.mcp,
@@ -2303,7 +2556,7 @@ async function createDocsMcpServer(options) {
2303
2556
  const defaultPages = dedupePages(await getSourcePages());
2304
2557
  const defaultTree = await getSourceNavigation();
2305
2558
  const defaultSkills = await getSourceSkills();
2306
- server.registerResource("docs-navigation", "docs://navigation", {
2559
+ registerResource("docs-navigation", "docs://navigation", {
2307
2560
  title: "Docs Navigation",
2308
2561
  description: "Structured navigation tree for the documentation site.",
2309
2562
  mimeType: "text/plain"
@@ -2314,7 +2567,7 @@ async function createDocsMcpServer(options) {
2314
2567
  }] }));
2315
2568
  for (const page of defaultPages) {
2316
2569
  const resourceUri = toPageResourceUri(page.url);
2317
- server.registerResource(`page-${slugToKey(page.slug)}`, resourceUri, {
2570
+ registerResource(`page-${slugToKey(page.slug)}`, resourceUri, {
2318
2571
  title: page.title,
2319
2572
  description: page.description,
2320
2573
  mimeType: "text/markdown"
@@ -2327,7 +2580,7 @@ async function createDocsMcpServer(options) {
2327
2580
  for (const skill of defaultSkills) for (const file of skill.files) {
2328
2581
  const encodedPath = file.path.split("/").map((segment) => encodeURIComponent(segment)).join("/");
2329
2582
  const resourceUri = `docs://skills/${encodeURIComponent(skill.name)}/${encodedPath}`;
2330
- server.registerResource(`skill-${encodeURIComponent(skill.name)}-${Buffer.from(file.path).toString("base64url")}`, resourceUri, {
2583
+ registerResource(`skill-${encodeURIComponent(skill.name)}-${Buffer.from(file.path).toString("base64url")}`, resourceUri, {
2331
2584
  title: `${skill.name}: ${file.path}`,
2332
2585
  description: `${skill.description} (${file.digest})`,
2333
2586
  mimeType: file.mediaType
@@ -2341,14 +2594,15 @@ async function createDocsMcpServer(options) {
2341
2594
  blob: Buffer.from(file.content).toString("base64")
2342
2595
  }] }));
2343
2596
  }
2344
- if (resolved.tools.listPages) server.registerTool("list_pages", {
2597
+ if (resolved.tools.listPages) registerTool("list_pages", {
2345
2598
  title: "List docs pages",
2346
2599
  description: "List the known documentation pages with titles, slugs, and URLs.",
2347
2600
  inputSchema: listPagesInputSchema,
2348
2601
  outputSchema: listPagesOutputSchema,
2349
2602
  annotations: { readOnlyHint: true }
2350
- }, async ({ locale }) => {
2603
+ }, async ({ locale, cursor }) => {
2351
2604
  const startedAt = nowMs();
2605
+ const resolvedLocale = resolveSourceLocale(locale);
2352
2606
  const trace = createDocsAgentTraceContext("mcp.tool.list_pages");
2353
2607
  const callSpanId = createDocsAgentTraceId("span");
2354
2608
  await emitDocsAgentTraceEvent(options.observability, {
@@ -2360,11 +2614,25 @@ async function createDocsMcpServer(options) {
2360
2614
  startedAt: trace.startedAt,
2361
2615
  status: "started",
2362
2616
  locale,
2363
- inputPreview: { locale },
2617
+ inputPreview: {
2618
+ locale,
2619
+ hasCursor: cursor !== void 0
2620
+ },
2364
2621
  metadata: { tool: "list_pages" }
2365
2622
  });
2366
2623
  try {
2367
- const pages = toPageSummaries(dedupePages(await getSourcePages(locale)));
2624
+ const page = paginateDocsMcpItems(toPageSummaries(dedupePages(await options.source.getPages(resolvedLocale, options.requestContext))).sort(compareDocsMcpPageSummaries), {
2625
+ kind: "mcp.tool/list_pages",
2626
+ scope: stableDocsMcpPaginationScope({ locale: resolvedLocale ?? null }),
2627
+ cursor
2628
+ });
2629
+ const result = {
2630
+ resultCount: page.resultCount,
2631
+ total: page.total,
2632
+ hasMore: page.hasMore,
2633
+ ...page.nextCursor ? { nextCursor: page.nextCursor } : {},
2634
+ pages: page.items
2635
+ };
2368
2636
  const elapsed = durationMs(startedAt);
2369
2637
  await emitDocsAnalyticsEvent(options.analytics, {
2370
2638
  type: "mcp_tool",
@@ -2372,13 +2640,15 @@ async function createDocsMcpServer(options) {
2372
2640
  locale,
2373
2641
  properties: {
2374
2642
  tool: "list_pages",
2375
- resultCount: pages.length,
2643
+ resultCount: page.resultCount,
2644
+ total: page.total,
2645
+ hasMore: page.hasMore,
2376
2646
  durationMs: elapsed
2377
2647
  }
2378
2648
  });
2379
2649
  trackMcpTool("list_pages", {
2380
2650
  locale,
2381
- resultCount: pages.length
2651
+ resultCount: page.resultCount
2382
2652
  });
2383
2653
  await emitDocsAgentTraceEvent(options.observability, {
2384
2654
  type: "tool.result",
@@ -2391,10 +2661,14 @@ async function createDocsMcpServer(options) {
2391
2661
  durationMs: elapsed,
2392
2662
  status: "success",
2393
2663
  locale,
2394
- outputPreview: { resultCount: pages.length },
2664
+ outputPreview: {
2665
+ resultCount: page.resultCount,
2666
+ total: page.total,
2667
+ hasMore: page.hasMore
2668
+ },
2395
2669
  metadata: { tool: "list_pages" }
2396
2670
  });
2397
- return createStructuredTextResult({ pages });
2671
+ return createStructuredTextResult(result);
2398
2672
  } catch (error) {
2399
2673
  const elapsed = durationMs(startedAt);
2400
2674
  await emitDocsAgentTraceEvent(options.observability, {
@@ -2414,14 +2688,15 @@ async function createDocsMcpServer(options) {
2414
2688
  throw error;
2415
2689
  }
2416
2690
  });
2417
- if (resolved.tools.listDocs) server.registerTool("list_docs", {
2691
+ if (resolved.tools.listDocs) registerTool("list_docs", {
2418
2692
  title: "List docs by section",
2419
2693
  description: "List documentation pages grouped by section, optionally narrowed to one section.",
2420
2694
  inputSchema: listDocsInputSchema,
2421
2695
  outputSchema: listDocsOutputSchema,
2422
2696
  annotations: { readOnlyHint: true }
2423
- }, async ({ section, locale }) => {
2697
+ }, async ({ section, locale, cursor }) => {
2424
2698
  const startedAt = nowMs();
2699
+ const resolvedLocale = resolveSourceLocale(locale);
2425
2700
  const trace = createDocsAgentTraceContext("mcp.tool.list_docs");
2426
2701
  const callSpanId = createDocsAgentTraceId("span");
2427
2702
  await emitDocsAgentTraceEvent(options.observability, {
@@ -2435,14 +2710,21 @@ async function createDocsMcpServer(options) {
2435
2710
  locale,
2436
2711
  inputPreview: {
2437
2712
  section,
2438
- locale
2713
+ locale,
2714
+ hasCursor: cursor !== void 0
2439
2715
  },
2440
2716
  metadata: { tool: "list_docs" }
2441
2717
  });
2442
2718
  try {
2443
- const docs = listDocsBySection(dedupePages(await getSourcePages(locale)), {
2719
+ const docs = paginateDocsMcpDocsList(listDocsBySection(dedupePages(await options.source.getPages(resolvedLocale, options.requestContext)), {
2444
2720
  section,
2445
2721
  entry: options.source.entry
2722
+ }), {
2723
+ cursor,
2724
+ scope: stableDocsMcpPaginationScope({
2725
+ locale: resolvedLocale ?? null,
2726
+ section: normalizeDocsListMatchValue(section ?? "")
2727
+ })
2446
2728
  });
2447
2729
  const elapsed = durationMs(startedAt);
2448
2730
  await emitDocsAnalyticsEvent(options.analytics, {
@@ -2453,6 +2735,8 @@ async function createDocsMcpServer(options) {
2453
2735
  tool: "list_docs",
2454
2736
  section,
2455
2737
  resultCount: docs.resultCount,
2738
+ total: docs.total,
2739
+ hasMore: docs.hasMore,
2456
2740
  sectionCount: docs.sectionCount,
2457
2741
  durationMs: elapsed
2458
2742
  }
@@ -2474,6 +2758,8 @@ async function createDocsMcpServer(options) {
2474
2758
  locale,
2475
2759
  outputPreview: {
2476
2760
  resultCount: docs.resultCount,
2761
+ total: docs.total,
2762
+ hasMore: docs.hasMore,
2477
2763
  sectionCount: docs.sectionCount
2478
2764
  },
2479
2765
  metadata: { tool: "list_docs" }
@@ -2498,14 +2784,15 @@ async function createDocsMcpServer(options) {
2498
2784
  throw error;
2499
2785
  }
2500
2786
  });
2501
- if (resolved.tools.listTasks) server.registerTool("list_tasks", {
2787
+ if (resolved.tools.listTasks) registerTool("list_tasks", {
2502
2788
  title: "List documented tasks",
2503
2789
  description: "List pages with actionable agent contracts, optionally filtered by text or applicability.",
2504
2790
  inputSchema: listTasksInputSchema,
2505
2791
  outputSchema: listTasksOutputSchema,
2506
2792
  annotations: { readOnlyHint: true }
2507
- }, async ({ query, framework, version, package: packageName, locale }) => {
2793
+ }, async ({ query, framework, version, package: packageName, locale, cursor }) => {
2508
2794
  const startedAt = nowMs();
2795
+ const resolvedLocale = resolveSourceLocale(locale);
2509
2796
  const trace = createDocsAgentTraceContext("mcp.tool.list_tasks");
2510
2797
  const callSpanId = createDocsAgentTraceId("span");
2511
2798
  await emitDocsAgentTraceEvent(options.observability, {
@@ -2521,20 +2808,34 @@ async function createDocsMcpServer(options) {
2521
2808
  queryLength: query?.length,
2522
2809
  framework,
2523
2810
  version,
2524
- package: packageName
2811
+ package: packageName,
2812
+ hasCursor: cursor !== void 0
2525
2813
  },
2526
2814
  metadata: { tool: "list_tasks" }
2527
2815
  });
2528
2816
  try {
2529
- const tasks = listDocsTasks(dedupePages(await getSourcePages(locale)), {
2817
+ const page = paginateDocsMcpItems(listDocsTasks(dedupePages(await options.source.getPages(resolvedLocale, options.requestContext)), {
2530
2818
  query,
2531
2819
  framework,
2532
2820
  version,
2533
2821
  package: packageName
2822
+ }).sort(compareDocsMcpTaskSummaries), {
2823
+ kind: "mcp.tool/list_tasks",
2824
+ scope: stableDocsMcpPaginationScope({
2825
+ locale: resolvedLocale ?? null,
2826
+ query: query?.trim().toLowerCase() ?? null,
2827
+ framework: framework?.trim().toLowerCase() ?? null,
2828
+ version: version?.trim().toLowerCase() ?? null,
2829
+ package: packageName?.trim().toLowerCase() ?? null
2830
+ }),
2831
+ cursor
2534
2832
  });
2535
2833
  const result = {
2536
- resultCount: tasks.length,
2537
- tasks
2834
+ resultCount: page.resultCount,
2835
+ total: page.total,
2836
+ hasMore: page.hasMore,
2837
+ ...page.nextCursor ? { nextCursor: page.nextCursor } : {},
2838
+ tasks: page.items
2538
2839
  };
2539
2840
  const elapsed = durationMs(startedAt);
2540
2841
  await emitDocsAnalyticsEvent(options.analytics, {
@@ -2547,13 +2848,15 @@ async function createDocsMcpServer(options) {
2547
2848
  framework,
2548
2849
  version,
2549
2850
  package: packageName,
2550
- resultCount: tasks.length,
2851
+ resultCount: page.resultCount,
2852
+ total: page.total,
2853
+ hasMore: page.hasMore,
2551
2854
  durationMs: elapsed
2552
2855
  }
2553
2856
  });
2554
2857
  trackMcpTool("list_tasks", {
2555
2858
  locale,
2556
- resultCount: tasks.length
2859
+ resultCount: page.resultCount
2557
2860
  });
2558
2861
  await emitDocsAgentTraceEvent(options.observability, {
2559
2862
  type: "tool.result",
@@ -2566,7 +2869,11 @@ async function createDocsMcpServer(options) {
2566
2869
  durationMs: elapsed,
2567
2870
  status: "success",
2568
2871
  locale,
2569
- outputPreview: { resultCount: tasks.length },
2872
+ outputPreview: {
2873
+ resultCount: page.resultCount,
2874
+ total: page.total,
2875
+ hasMore: page.hasMore
2876
+ },
2570
2877
  metadata: { tool: "list_tasks" }
2571
2878
  });
2572
2879
  return {
@@ -2595,7 +2902,7 @@ async function createDocsMcpServer(options) {
2595
2902
  throw error;
2596
2903
  }
2597
2904
  });
2598
- if (resolved.tools.readTask) server.registerTool("read_task", {
2905
+ if (resolved.tools.readTask) registerTool("read_task", {
2599
2906
  title: "Read a documented task",
2600
2907
  description: "Read the full structured agent contract for a page by slug or URL path.",
2601
2908
  inputSchema: readTaskInputSchema,
@@ -2744,7 +3051,7 @@ async function createDocsMcpServer(options) {
2744
3051
  throw error;
2745
3052
  }
2746
3053
  });
2747
- if (resolved.tools.getNavigation) server.registerTool("get_navigation", {
3054
+ if (resolved.tools.getNavigation) registerTool("get_navigation", {
2748
3055
  title: "Get docs navigation",
2749
3056
  description: "Return the documentation navigation tree for the current docs site.",
2750
3057
  inputSchema: getNavigationInputSchema,
@@ -2817,7 +3124,7 @@ async function createDocsMcpServer(options) {
2817
3124
  throw error;
2818
3125
  }
2819
3126
  });
2820
- if (resolved.tools.getConfigSchema) server.registerTool("get_config_schema", {
3127
+ if (resolved.tools.getConfigSchema) registerTool("get_config_schema", {
2821
3128
  title: "Get docs config schema",
2822
3129
  description: "Return structured docs.config.ts option metadata, optionally filtered by option path or query.",
2823
3130
  inputSchema: getConfigSchemaInputSchema,
@@ -2892,13 +3199,119 @@ async function createDocsMcpServer(options) {
2892
3199
  throw error;
2893
3200
  }
2894
3201
  });
2895
- if (resolved.tools.searchDocs) server.registerTool("search_docs", {
3202
+ if (resolved.tools.searchFacets) registerTool("list_search_facets", {
3203
+ title: "List documentation search facets",
3204
+ description: "List valid framework, version, package, and tag filters with matching page counts before calling search_docs. Use facet, limit, and cursor to continue a large facet without document bodies.",
3205
+ inputSchema: searchFacetsInputSchema,
3206
+ outputSchema: searchFacetsOutputSchema,
3207
+ annotations: { readOnlyHint: true }
3208
+ }, async ({ locale, audience, facet, limit, cursor, framework, version, package: packageName, tags }) => {
3209
+ const startedAt = nowMs();
3210
+ const resolvedAudience = audience ?? "agent";
3211
+ const resolvedLocale = resolveSourceLocale(locale);
3212
+ const filters = {
3213
+ framework,
3214
+ version,
3215
+ package: packageName,
3216
+ tags
3217
+ };
3218
+ const trace = createDocsAgentTraceContext("mcp.tool.list_search_facets");
3219
+ const callSpanId = createDocsAgentTraceId("span");
3220
+ await emitDocsAgentTraceEvent(options.observability, {
3221
+ type: "tool.call",
3222
+ source: "mcp",
3223
+ traceId: trace.traceId,
3224
+ spanId: callSpanId,
3225
+ name: "list_search_facets",
3226
+ startedAt: trace.startedAt,
3227
+ status: "started",
3228
+ locale,
3229
+ inputPreview: {
3230
+ locale,
3231
+ audience: resolvedAudience,
3232
+ facet,
3233
+ limit,
3234
+ hasCursor: cursor !== void 0,
3235
+ filterFields: Object.entries(filters).filter(([, value]) => value !== void 0).map(([field]) => field)
3236
+ },
3237
+ metadata: { tool: "list_search_facets" }
3238
+ });
3239
+ try {
3240
+ const facets = await buildDocsSearchFacets({
3241
+ pages: toSearchSourcePages(dedupePages(await options.source.getPages(resolvedLocale, options.requestContext))),
3242
+ search: toolSearchConfig ?? true,
3243
+ audience: resolvedAudience,
3244
+ filters,
3245
+ facet,
3246
+ limit,
3247
+ cursor,
3248
+ locale: resolvedLocale,
3249
+ baseUrl: options.source.baseUrl ?? (options.requestContext?.request ? new URL(options.requestContext.request.url).origin : void 0)
3250
+ });
3251
+ const elapsed = durationMs(startedAt);
3252
+ await emitDocsAnalyticsEvent(options.analytics, {
3253
+ type: "mcp_tool",
3254
+ source: "mcp",
3255
+ locale,
3256
+ properties: {
3257
+ tool: "list_search_facets",
3258
+ audience: resolvedAudience,
3259
+ matchedPageCount: facets.matchedPageCount,
3260
+ frameworkCount: facets.facets.framework.valueCount,
3261
+ versionCount: facets.facets.version.valueCount,
3262
+ packageCount: facets.facets.package.valueCount,
3263
+ tagCount: facets.facets.tags.valueCount,
3264
+ durationMs: elapsed
3265
+ }
3266
+ });
3267
+ trackMcpTool("list_search_facets", {
3268
+ locale,
3269
+ resultCount: facets.matchedPageCount
3270
+ });
3271
+ await emitDocsAgentTraceEvent(options.observability, {
3272
+ type: "tool.result",
3273
+ source: "mcp",
3274
+ traceId: trace.traceId,
3275
+ parentSpanId: callSpanId,
3276
+ name: "list_search_facets",
3277
+ startedAt: trace.startedAt,
3278
+ endedAt: (/* @__PURE__ */ new Date()).toISOString(),
3279
+ durationMs: elapsed,
3280
+ status: "success",
3281
+ locale,
3282
+ outputPreview: {
3283
+ matchedPageCount: facets.matchedPageCount,
3284
+ facetValueCount: Object.values(facets.facets).reduce((total, facet) => total + facet.valueCount, 0)
3285
+ },
3286
+ metadata: { tool: "list_search_facets" }
3287
+ });
3288
+ return createStructuredTextResult(facets);
3289
+ } catch (error) {
3290
+ const elapsed = durationMs(startedAt);
3291
+ await emitDocsAgentTraceEvent(options.observability, {
3292
+ type: "tool.error",
3293
+ source: "mcp",
3294
+ traceId: trace.traceId,
3295
+ parentSpanId: callSpanId,
3296
+ name: "list_search_facets",
3297
+ startedAt: trace.startedAt,
3298
+ endedAt: (/* @__PURE__ */ new Date()).toISOString(),
3299
+ durationMs: elapsed,
3300
+ status: "error",
3301
+ locale,
3302
+ outputPreview: { message: error instanceof Error ? error.message : "Unknown error" },
3303
+ metadata: { tool: "list_search_facets" }
3304
+ });
3305
+ throw error;
3306
+ }
3307
+ });
3308
+ if (resolved.tools.searchDocs) registerTool("search_docs", {
2896
3309
  title: "Search documentation",
2897
3310
  description: "Search the docs by keyword with optional framework, version, package, and tag filters. Returns structured ambiguity and metadata warnings.",
2898
3311
  inputSchema: searchDocsInputSchema,
2899
3312
  outputSchema: searchDocsOutputSchema,
2900
3313
  annotations: { readOnlyHint: true }
2901
- }, async ({ query, limit, locale, audience, framework, version, package: packageName, tags }) => {
3314
+ }, async ({ query, limit, cursor, locale, audience, framework, version, package: packageName, tags }) => {
2902
3315
  const startedAt = nowMs();
2903
3316
  const resolvedLimit = limit ?? 10;
2904
3317
  const resolvedAudience = audience ?? "agent";
@@ -2923,6 +3336,7 @@ async function createDocsMcpServer(options) {
2923
3336
  inputPreview: {
2924
3337
  queryLength: query.length,
2925
3338
  limit: resolvedLimit,
3339
+ hasCursor: cursor !== void 0,
2926
3340
  locale,
2927
3341
  audience: resolvedAudience,
2928
3342
  filterFields: Object.entries(filters).filter(([, value]) => value !== void 0).map(([field]) => field)
@@ -2940,7 +3354,8 @@ async function createDocsMcpServer(options) {
2940
3354
  siteTitle: options.source.siteTitle,
2941
3355
  baseUrl: options.source.baseUrl ?? (options.requestContext?.request ? new URL(options.requestContext.request.url).origin : void 0),
2942
3356
  syncBaseUrl: options.source.baseUrl ?? null,
2943
- limit: resolvedLimit
3357
+ limit: resolvedLimit,
3358
+ cursor
2944
3359
  });
2945
3360
  const elapsed = durationMs(startedAt);
2946
3361
  await emitDocsAnalyticsEvent(options.analytics, {
@@ -2954,6 +3369,8 @@ async function createDocsMcpServer(options) {
2954
3369
  limit: resolvedLimit,
2955
3370
  audience: resolvedAudience,
2956
3371
  resultCount: searchResponse.resultCount,
3372
+ total: searchResponse.total,
3373
+ hasMore: searchResponse.hasMore,
2957
3374
  warningCount: searchResponse.warnings.length,
2958
3375
  durationMs: elapsed
2959
3376
  }
@@ -2975,6 +3392,8 @@ async function createDocsMcpServer(options) {
2975
3392
  locale,
2976
3393
  outputPreview: {
2977
3394
  resultCount: searchResponse.resultCount,
3395
+ total: searchResponse.total,
3396
+ hasMore: searchResponse.hasMore,
2978
3397
  warningCount: searchResponse.warnings.length
2979
3398
  },
2980
3399
  metadata: { tool: "search_docs" }
@@ -2999,7 +3418,7 @@ async function createDocsMcpServer(options) {
2999
3418
  throw error;
3000
3419
  }
3001
3420
  });
3002
- if (resolved.tools.getCodeExamples) server.registerTool("get_code_examples", {
3421
+ if (resolved.tools.getCodeExamples) registerTool("get_code_examples", {
3003
3422
  title: "Get docs code examples",
3004
3423
  description: "Return fenced code examples from the docs with parsed metadata such as title, framework, packageManager, and runnable.",
3005
3424
  inputSchema: getCodeExamplesInputSchema,
@@ -3098,7 +3517,7 @@ async function createDocsMcpServer(options) {
3098
3517
  throw error;
3099
3518
  }
3100
3519
  });
3101
- if (resolved.tools.getContext) server.registerTool("get_context", {
3520
+ if (resolved.tools.getContext) registerTool("get_context", {
3102
3521
  title: "Get budgeted docs context",
3103
3522
  description: "Build deterministic, section-level documentation context for a query within a conservative UTF-8 byte ceiling derived from the requested token budget, with source URLs and accounting metadata.",
3104
3523
  inputSchema: getContextInputSchema,
@@ -3208,13 +3627,13 @@ async function createDocsMcpServer(options) {
3208
3627
  throw error;
3209
3628
  }
3210
3629
  });
3211
- if (resolved.tools.listPageSections !== false) server.registerTool("list_page_sections", {
3630
+ if (resolved.tools.listPageSections !== false) registerTool("list_page_sections", {
3212
3631
  title: "List sections in a docs page",
3213
3632
  description: "Discover a page's canonical headings, anchors, hierarchy, line ranges, size estimates, and budget-aware fetch URLs without returning the page body.",
3214
3633
  inputSchema: listPageSectionsInputSchema,
3215
3634
  outputSchema: pageSectionIndexOutputSchema,
3216
3635
  annotations: { readOnlyHint: true }
3217
- }, async ({ path: requestedPath, locale, tokenBudget, byteBudget }) => {
3636
+ }, async ({ path: requestedPath, locale, tokenBudget, byteBudget, cursor }) => {
3218
3637
  const startedAt = nowMs();
3219
3638
  const resolvedLocale = resolveSourceLocale(locale);
3220
3639
  const trace = createDocsAgentTraceContext("mcp.tool.list_page_sections");
@@ -3233,7 +3652,8 @@ async function createDocsMcpServer(options) {
3233
3652
  locale,
3234
3653
  resolvedLocale,
3235
3654
  tokenBudget,
3236
- byteBudget
3655
+ byteBudget,
3656
+ hasCursor: cursor !== void 0
3237
3657
  },
3238
3658
  metadata: { tool: "list_page_sections" }
3239
3659
  });
@@ -3286,12 +3706,31 @@ async function createDocsMcpServer(options) {
3286
3706
  }
3287
3707
  const document = upsertPageAgentContractMarkdown(getDocsMcpSourceMarkdown(page), page.agent);
3288
3708
  const publicBaseUrl = options.source.baseUrl ?? (options.requestContext?.request ? new URL(options.requestContext.request.url).origin : void 0);
3289
- const sectionIndex = buildDocsMarkdownSectionIndex(document, {
3709
+ const completeSectionIndex = buildDocsMarkdownSectionIndex(document, {
3290
3710
  canonicalUrl: resolveDocsMcpPublicUrl(withDocsMcpUrlLocale(page.url, resolvedLocale), publicBaseUrl),
3291
3711
  markdownUrl: resolveDocsMcpPublicUrl(toDocsMcpPageMarkdownUrl(page.url, resolvedLocale), publicBaseUrl),
3292
3712
  tokenBudget,
3293
3713
  byteBudget
3294
3714
  });
3715
+ const sectionPage = paginateDocsMcpItems(completeSectionIndex.sections, {
3716
+ kind: "mcp.tool/list_page_sections",
3717
+ scope: stableDocsMcpPaginationScope({
3718
+ path: page.url,
3719
+ locale: resolvedLocale ?? null,
3720
+ tokenBudget: tokenBudget ?? null,
3721
+ byteBudget: byteBudget ?? null
3722
+ }),
3723
+ cursor
3724
+ });
3725
+ const sectionIndex = {
3726
+ ...completeSectionIndex,
3727
+ sectionCount: completeSectionIndex.sectionCount,
3728
+ resultCount: sectionPage.resultCount,
3729
+ total: sectionPage.total,
3730
+ hasMore: sectionPage.hasMore,
3731
+ ...sectionPage.nextCursor ? { nextCursor: sectionPage.nextCursor } : {},
3732
+ sections: sectionPage.items
3733
+ };
3295
3734
  const elapsed = durationMs(startedAt);
3296
3735
  await emitDocsAnalyticsEvent(options.analytics, {
3297
3736
  type: "mcp_tool",
@@ -3304,6 +3743,8 @@ async function createDocsMcpServer(options) {
3304
3743
  slug: page.slug,
3305
3744
  found: true,
3306
3745
  sectionCount: sectionIndex.sectionCount,
3746
+ total: sectionIndex.total,
3747
+ hasMore: sectionIndex.hasMore,
3307
3748
  estimatedTokens: sectionIndex.estimatedTokens,
3308
3749
  utf8Bytes: sectionIndex.utf8Bytes,
3309
3750
  tokenBudget,
@@ -3313,7 +3754,7 @@ async function createDocsMcpServer(options) {
3313
3754
  });
3314
3755
  trackMcpTool("list_page_sections", {
3315
3756
  locale: resolvedLocale,
3316
- resultCount: sectionIndex.sectionCount
3757
+ resultCount: sectionIndex.resultCount
3317
3758
  });
3318
3759
  await emitDocsAgentTraceEvent(options.observability, {
3319
3760
  type: "tool.result",
@@ -3331,6 +3772,8 @@ async function createDocsMcpServer(options) {
3331
3772
  found: true,
3332
3773
  slug: page.slug,
3333
3774
  sectionCount: sectionIndex.sectionCount,
3775
+ total: sectionIndex.total,
3776
+ hasMore: sectionIndex.hasMore,
3334
3777
  estimatedTokens: sectionIndex.estimatedTokens,
3335
3778
  utf8Bytes: sectionIndex.utf8Bytes
3336
3779
  },
@@ -3356,7 +3799,7 @@ async function createDocsMcpServer(options) {
3356
3799
  throw error;
3357
3800
  }
3358
3801
  });
3359
- if (resolved.tools.readPage) server.registerTool("read_page", {
3802
+ if (resolved.tools.readPage) registerTool("read_page", {
3360
3803
  title: "Read a docs page",
3361
3804
  description: "Read a documentation page by slug or URL path, optionally selecting one heading and limiting returned characters.",
3362
3805
  inputSchema: readPageInputSchema,
@@ -4371,6 +4814,18 @@ function toDocsListPageSummary(page) {
4371
4814
  lastModified: page.lastModified
4372
4815
  };
4373
4816
  }
4817
+ function compareDocsMcpPageSummaries(left, right) {
4818
+ const urlOrder = left.url.localeCompare(right.url);
4819
+ if (urlOrder !== 0) return urlOrder;
4820
+ const slugOrder = left.slug.localeCompare(right.slug);
4821
+ return slugOrder !== 0 ? slugOrder : left.title.localeCompare(right.title);
4822
+ }
4823
+ function compareDocsMcpTaskSummaries(left, right) {
4824
+ const urlOrder = left.url.localeCompare(right.url);
4825
+ if (urlOrder !== 0) return urlOrder;
4826
+ const slugOrder = left.slug.localeCompare(right.slug);
4827
+ return slugOrder !== 0 ? slugOrder : left.title.localeCompare(right.title);
4828
+ }
4374
4829
  function listDocsTasks(pages, filters) {
4375
4830
  const query = filters.query?.toLowerCase();
4376
4831
  const applicabilityFilters = [
@@ -4412,6 +4867,8 @@ function listDocsBySection(pages, filters) {
4412
4867
  const requestedSection = filters.section?.trim();
4413
4868
  if (!requestedSection) return {
4414
4869
  resultCount: allPages.length,
4870
+ total: allPages.length,
4871
+ hasMore: false,
4415
4872
  sectionCount: countDocsSections(tree.sections),
4416
4873
  pages: allPages,
4417
4874
  rootPages: tree.rootPages,
@@ -4424,6 +4881,8 @@ function listDocsBySection(pages, filters) {
4424
4881
  return {
4425
4882
  section: requestedSection,
4426
4883
  resultCount: matchedPages.length,
4884
+ total: matchedPages.length,
4885
+ hasMore: false,
4427
4886
  sectionCount: countDocsSections(sections),
4428
4887
  pages: matchedPages,
4429
4888
  rootPages: [],
@@ -4434,6 +4893,8 @@ function listDocsBySection(pages, filters) {
4434
4893
  if (page) return {
4435
4894
  section: requestedSection,
4436
4895
  resultCount: 1,
4896
+ total: 1,
4897
+ hasMore: false,
4437
4898
  sectionCount: 0,
4438
4899
  pages: [page],
4439
4900
  rootPages: [page],
@@ -4442,12 +4903,46 @@ function listDocsBySection(pages, filters) {
4442
4903
  return {
4443
4904
  section: requestedSection,
4444
4905
  resultCount: 0,
4906
+ total: 0,
4907
+ hasMore: false,
4445
4908
  sectionCount: 0,
4446
4909
  pages: [],
4447
4910
  rootPages: [],
4448
4911
  sections: []
4449
4912
  };
4450
4913
  }
4914
+ function paginateDocsMcpDocsList(docs, options) {
4915
+ const page = paginateDocsMcpItems([...docs.pages].sort(compareDocsMcpPageSummaries), {
4916
+ kind: "mcp.tool/list_docs",
4917
+ scope: options.scope,
4918
+ cursor: options.cursor
4919
+ });
4920
+ const pageUrls = new Set(page.items.map((item) => item.url));
4921
+ const sections = pruneDocsMcpSections(docs.sections, pageUrls);
4922
+ return {
4923
+ ...docs,
4924
+ resultCount: page.resultCount,
4925
+ total: page.total,
4926
+ hasMore: page.hasMore,
4927
+ ...page.nextCursor ? { nextCursor: page.nextCursor } : {},
4928
+ pages: page.items,
4929
+ rootPages: docs.rootPages.filter((item) => pageUrls.has(item.url)),
4930
+ sections,
4931
+ sectionCount: docs.sectionCount
4932
+ };
4933
+ }
4934
+ function pruneDocsMcpSections(sections, pageUrls) {
4935
+ return sections.flatMap((section) => {
4936
+ const pages = section.pages.filter((page) => pageUrls.has(page.url));
4937
+ const childSections = pruneDocsMcpSections(section.sections, pageUrls);
4938
+ if (pages.length === 0 && childSections.length === 0) return [];
4939
+ return [{
4940
+ ...section,
4941
+ pages,
4942
+ sections: childSections
4943
+ }];
4944
+ });
4945
+ }
4451
4946
  function buildDocsSectionTree(pages) {
4452
4947
  const sectionSlugs = /* @__PURE__ */ new Set();
4453
4948
  for (const page of pages) {