@farming-labs/docs 0.2.74 → 0.2.77

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 (38) hide show
  1. package/dist/{agent-D0LEY2Vy.mjs → agent-D4hNGatV.mjs} +2 -2
  2. package/dist/agent-DRKj42ht.d.mts +938 -0
  3. package/dist/{agent-BJ0KXN-A.mjs → agent-SRIJ-oF8.mjs} +28 -6
  4. package/dist/{agent-evals-LIScgQVm.mjs → agent-evals-DmFoKy_y.mjs} +1 -1
  5. package/dist/{agent-export-B92pNe6g.mjs → agent-export-CM-p9_YD.mjs} +7 -7
  6. package/dist/agent-skills-bundle.d.mts +1 -1
  7. package/dist/{agent-skills-server-noFn1l_p.mjs → agent-skills-server-B2EXn2QD.mjs} +1 -1
  8. package/dist/{agent-skills-server-BqvIGQb1.d.mts → agent-skills-server-CJ4D0G9P.d.mts} +2 -2
  9. package/dist/agent-skills-vite.d.mts +3 -3
  10. package/dist/agent-skills-vite.mjs +2 -2
  11. package/dist/{agents-C228QGbp.mjs → agents-BeLDNCnE.mjs} +7 -16
  12. package/dist/cli/index.mjs +15 -15
  13. package/dist/client/react.d.mts +1 -1
  14. package/dist/{cloud-ask-ai-6X9pVjIY.d.mts → cloud-ask-ai-BCgHPxew.d.mts} +1 -1
  15. package/dist/{dev-Cc1cFFj-.mjs → dev-kjaxOOla.mjs} +8 -2
  16. package/dist/docs-cloud-server.d.mts +2 -2
  17. package/dist/{doctor-CgrUZ3tY.mjs → doctor-DhMVKHjp.mjs} +57 -24
  18. package/dist/{golden-evaluations-DTEPZeb0.mjs → golden-evaluations-VzE6juQl.mjs} +7 -3
  19. package/dist/index.d.mts +5 -876
  20. package/dist/index.mjs +6 -6
  21. package/dist/{mcp-YD37HlPY.mjs → mcp-CZkQJXtA.mjs} +7 -6
  22. package/dist/mcp.d.mts +30 -5
  23. package/dist/mcp.mjs +594 -69
  24. package/dist/{prompt-references-BIS1eXt4.mjs → prompt-references-B6L3AD98.mjs} +3 -3
  25. package/dist/{retrieval-digest-9Kp3uSaZ.d.mts → retrieval-digest-rponR4jM.d.mts} +24 -50
  26. package/dist/{review-Dbzkdl6K.mjs → review-nxoV8jIm.mjs} +5 -5
  27. package/dist/{robots-RbgmSvPH.mjs → robots-CgH6Stqn.mjs} +2 -2
  28. package/dist/{robots-DUO0r4UI.mjs → robots-CiQT8mX2.mjs} +3 -3
  29. package/dist/{search-hcqaEZVN.mjs → search-BTUIkhnT.mjs} +6 -6
  30. package/dist/{search-DIyIl-tE.mjs → search-Da2AKaFJ.mjs} +624 -122
  31. package/dist/server.d.mts +9 -6
  32. package/dist/server.mjs +7 -7
  33. package/dist/{sitemap-jhkS_jYx.mjs → sitemap-e0_GeCWJ.mjs} +6 -6
  34. package/dist/{sitemap-server-BfyBM7_V.mjs → sitemap-server-DHaxyPAd.mjs} +12 -3
  35. package/dist/{standards-discovery-BwL5ffGx.mjs → standards-discovery-BKlEnK_H.mjs} +32 -4
  36. package/dist/{standards-discovery-CdrOE-05.d.mts → standards-discovery-BbslY4id.d.mts} +34 -7
  37. package/dist/{types-clg5J1R4.d.mts → types-B-5Kzmkn.d.mts} +73 -1
  38. package/package.json +1 -1
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 { Kt as normalizeDocsRelated, 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 } from "./agent-BJ0KXN-A.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-SRIJ-oF8.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-DIyIl-tE.mjs";
4
+ import "./standards-discovery-BKlEnK_H.mjs";
5
+ import { A as DocsPaginationCursorError, B as stripGeneratedAgentProvenance, D as normalizeAgentLocale, E as normalizeAgentFramework, G as emitDocsTelemetryAgentSurfaceEvent, H as resolvePageSidebarFolderIndexBehavior, J as emitDocsTelemetryProjectEvent, N as isDocsRetrievalCanonicalUrl, O as normalizeAgentScopeValues, T as agentVersionConstraintsOverlap, h as performDocsSearchWithMetadata, j as paginateDocsItems, m as performDocsSearch, p as normalizeDocsSearchFilters, q as emitDocsTelemetryMcpToolEvent, w as agentVersionConstraintMatches } from "./search-Da2AKaFJ.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;
@@ -1488,6 +1491,13 @@ const DOCS_CONFIG_SCHEMA_OPTIONS_TEMPLATE = [
1488
1491
  default: true,
1489
1492
  description: "Expose the list_pages tool."
1490
1493
  },
1494
+ {
1495
+ path: "mcp.tools.listPageSections",
1496
+ name: "listPageSections",
1497
+ type: "boolean",
1498
+ default: true,
1499
+ description: "Expose the body-free list_page_sections discovery tool for page headings, anchors, hierarchy, sizes, and follow-up fetch URLs."
1500
+ },
1491
1501
  {
1492
1502
  path: "mcp.tools.listTasks",
1493
1503
  name: "listTasks",
@@ -1555,17 +1565,26 @@ const DOCS_CONFIG_SCHEMA_OPTIONS_TEMPLATE = [
1555
1565
  default: false,
1556
1566
  description: "Generated API reference pages from framework route conventions or a hosted OpenAPI document.",
1557
1567
  docs: "/docs/customization/api-reference",
1558
- children: [{
1559
- path: "apiReference.specUrl",
1560
- name: "specUrl",
1561
- type: "string",
1562
- description: "Remote OpenAPI JSON URL when the backend owns the schema."
1563
- }, {
1564
- path: "apiReference.path",
1565
- name: "path",
1566
- type: "string",
1567
- description: "Docs route where the API reference is rendered."
1568
- }]
1568
+ children: [
1569
+ {
1570
+ path: "apiReference.specUrl",
1571
+ name: "specUrl",
1572
+ type: "string",
1573
+ description: "Remote OpenAPI JSON URL when the backend owns the schema."
1574
+ },
1575
+ {
1576
+ path: "apiReference.path",
1577
+ name: "path",
1578
+ type: "string",
1579
+ description: "Docs route where the API reference is rendered."
1580
+ },
1581
+ {
1582
+ path: "apiReference.catalogTargets",
1583
+ name: "catalogTargets",
1584
+ type: "string[]",
1585
+ description: "Product API base URLs that the OpenAPI document describes in the RFC 9727 catalog."
1586
+ }
1587
+ ]
1569
1588
  },
1570
1589
  {
1571
1590
  path: "codeBlocks",
@@ -1697,9 +1716,11 @@ export default defineDocs({
1697
1716
  }
1698
1717
  ]);
1699
1718
  const searchFilterValueSchema = z.union([z.string().trim().min(1).max(128), z.array(z.string().trim().min(1).max(128)).min(1).max(16)]);
1719
+ const paginationCursorInputSchema = z.string().min(1).max(4096).describe("Opaque nextCursor from the preceding response page.").optional();
1700
1720
  const searchDocsInputSchema = z.object({
1701
1721
  query: z.string().trim().min(1),
1702
1722
  limit: z.number().int().min(1).max(25).optional(),
1723
+ cursor: paginationCursorInputSchema,
1703
1724
  locale: z.string().trim().min(1).max(128).optional(),
1704
1725
  audience: z.enum(["human", "agent"]).optional(),
1705
1726
  framework: searchFilterValueSchema.optional(),
@@ -1713,12 +1734,20 @@ const readPageInputSchema = z.object({
1713
1734
  section: z.string().trim().min(1).optional(),
1714
1735
  maxChars: z.number().int().min(256).max(1e6).optional()
1715
1736
  });
1737
+ const listPageSectionsInputSchema = z.object({
1738
+ path: z.string().min(1),
1739
+ locale: z.string().trim().min(1).max(128).optional(),
1740
+ tokenBudget: z.number().int().min(1).max(1e6).optional(),
1741
+ byteBudget: z.number().int().min(1).max(1e6).optional(),
1742
+ cursor: paginationCursorInputSchema
1743
+ });
1716
1744
  const listTasksInputSchema = z.object({
1717
1745
  query: z.string().trim().min(1).optional(),
1718
1746
  framework: z.string().trim().min(1).optional(),
1719
1747
  version: z.string().trim().min(1).optional(),
1720
1748
  package: z.string().trim().min(1).optional(),
1721
- locale: z.string().trim().min(1).max(128).optional()
1749
+ locale: z.string().trim().min(1).max(128).optional(),
1750
+ cursor: paginationCursorInputSchema
1722
1751
  });
1723
1752
  const readTaskInputSchema = readPageInputSchema;
1724
1753
  const pageAgentAppliesToOutputSchema = z.object({
@@ -1762,8 +1791,14 @@ const taskSummaryOutputSchema = z.object({
1762
1791
  outcome: z.string().optional(),
1763
1792
  appliesTo: pageAgentAppliesToOutputSchema.optional()
1764
1793
  });
1794
+ const paginationOutputShape = {
1795
+ resultCount: z.number().int().nonnegative().describe("Items returned in this response page."),
1796
+ total: z.number().int().nonnegative().describe("Items in the complete filtered result set."),
1797
+ hasMore: z.boolean().describe("Whether another response page is available."),
1798
+ nextCursor: z.string().describe("Opaque cursor to pass as cursor on the next request.").optional()
1799
+ };
1765
1800
  const listTasksOutputSchema = z.object({
1766
- resultCount: z.number().int().nonnegative(),
1801
+ ...paginationOutputShape,
1767
1802
  tasks: z.array(taskSummaryOutputSchema)
1768
1803
  });
1769
1804
  const readTaskOutputSchema = z.object({
@@ -1777,10 +1812,14 @@ const readTaskOutputSchema = z.object({
1777
1812
  }),
1778
1813
  contract: pageAgentContractOutputSchema
1779
1814
  });
1780
- const listPagesInputSchema = z.object({ locale: z.string().trim().min(1).max(128).optional() });
1815
+ const listPagesInputSchema = z.object({
1816
+ locale: z.string().trim().min(1).max(128).optional(),
1817
+ cursor: paginationCursorInputSchema
1818
+ });
1781
1819
  const listDocsInputSchema = z.object({
1782
1820
  section: z.string().trim().min(1).optional(),
1783
- locale: z.string().trim().min(1).max(128).optional()
1821
+ locale: z.string().trim().min(1).max(128).optional(),
1822
+ cursor: paginationCursorInputSchema
1784
1823
  });
1785
1824
  const getNavigationInputSchema = z.object({ locale: z.string().trim().min(1).max(128).optional() });
1786
1825
  const getConfigSchemaInputSchema = z.object({
@@ -1833,10 +1872,13 @@ const docsSectionOutputSchema = z.lazy(() => z.object({
1833
1872
  pages: z.array(pageSummaryOutputSchema),
1834
1873
  sections: z.array(docsSectionOutputSchema)
1835
1874
  }));
1836
- const listPagesOutputSchema = z.object({ pages: z.array(pageSummaryOutputSchema) });
1875
+ const listPagesOutputSchema = z.object({
1876
+ ...paginationOutputShape,
1877
+ pages: z.array(pageSummaryOutputSchema)
1878
+ });
1837
1879
  const listDocsOutputSchema = z.object({
1880
+ ...paginationOutputShape,
1838
1881
  section: z.string().optional(),
1839
- resultCount: z.number().int().nonnegative(),
1840
1882
  sectionCount: z.number().int().nonnegative(),
1841
1883
  pages: z.array(pageSummaryOutputSchema),
1842
1884
  rootPages: z.array(pageSummaryOutputSchema),
@@ -1938,7 +1980,7 @@ const searchDocsOutputSchema = z.object({
1938
1980
  audience: z.enum(["human", "agent"]),
1939
1981
  filters: searchFiltersOutputSchema,
1940
1982
  indexGeneration: retrievalSourceDigestOutputSchema,
1941
- resultCount: z.number().int().nonnegative(),
1983
+ ...paginationOutputShape,
1942
1984
  results: z.array(searchResultOutputSchema),
1943
1985
  warnings: z.array(searchWarningOutputSchema)
1944
1986
  });
@@ -2013,6 +2055,35 @@ const readPageOutputSchema = z.object({
2013
2055
  totalChars: z.number().int().nonnegative(),
2014
2056
  truncated: z.boolean()
2015
2057
  });
2058
+ const pageSectionMetadataOutputSchema = z.object({
2059
+ id: z.string(),
2060
+ heading: z.string(),
2061
+ level: z.number().int().positive(),
2062
+ parentId: z.string().optional(),
2063
+ startLine: z.number().int().positive(),
2064
+ endLine: z.number().int().positive(),
2065
+ estimatedTokens: z.number().int().nonnegative(),
2066
+ utf8Bytes: z.number().int().nonnegative(),
2067
+ canonicalUrl: z.string(),
2068
+ markdownUrl: z.string()
2069
+ });
2070
+ const pageSectionIndexOutputSchema = z.object({
2071
+ schemaVersion: z.literal(2),
2072
+ format: z.literal(DOCS_MARKDOWN_SECTION_INDEX_FORMAT),
2073
+ canonicalUrl: z.string(),
2074
+ markdownUrl: z.string(),
2075
+ sectionIndexUrl: z.string(),
2076
+ lineNumbering: z.literal("body"),
2077
+ sectionCount: z.number().int().nonnegative(),
2078
+ ...paginationOutputShape,
2079
+ estimatedTokens: z.number().int().nonnegative(),
2080
+ utf8Bytes: z.number().int().nonnegative(),
2081
+ fetchBudget: z.object({
2082
+ tokenBudget: z.number().int().positive().optional(),
2083
+ byteBudget: z.number().int().positive().optional()
2084
+ }).optional(),
2085
+ sections: z.array(pageSectionMetadataOutputSchema)
2086
+ });
2016
2087
  const contextSourceOutputSchema = z.object({
2017
2088
  id: z.string(),
2018
2089
  title: z.string(),
@@ -2069,6 +2140,7 @@ function resolveDocsMcpConfig(mcp, defaults = {}) {
2069
2140
  tools: {
2070
2141
  listDocs: true,
2071
2142
  listPages: true,
2143
+ listPageSections: true,
2072
2144
  readPage: true,
2073
2145
  listTasks: true,
2074
2146
  readTask: true,
@@ -2089,6 +2161,7 @@ function resolveDocsMcpConfig(mcp, defaults = {}) {
2089
2161
  tools: {
2090
2162
  listDocs: config.tools?.listDocs ?? true,
2091
2163
  listPages: config.tools?.listPages ?? true,
2164
+ listPageSections: config.tools?.listPageSections ?? true,
2092
2165
  readPage: config.tools?.readPage ?? true,
2093
2166
  listTasks: config.tools?.listTasks ?? true,
2094
2167
  readTask: config.tools?.readTask ?? true,
@@ -2220,16 +2293,155 @@ function createStructuredTextResult(structuredContent, text) {
2220
2293
  structuredContent
2221
2294
  };
2222
2295
  }
2296
+ function stableDocsMcpPaginationScope(values) {
2297
+ return JSON.stringify(values);
2298
+ }
2299
+ function docsMcpPaginationSnapshot(values) {
2300
+ return JSON.stringify(values);
2301
+ }
2302
+ function paginateDocsMcpItems(items, options) {
2303
+ return paginateDocsItems(items, {
2304
+ kind: options.kind,
2305
+ scope: options.scope,
2306
+ snapshot: docsMcpPaginationSnapshot(items),
2307
+ cursor: options.cursor,
2308
+ pageSize: options.pageSize ?? DOCS_MCP_TOOL_LIST_PAGE_SIZE
2309
+ });
2310
+ }
2311
+ function paginateDocsMcpProtocolItems(items, options) {
2312
+ try {
2313
+ return paginateDocsMcpItems(items, {
2314
+ ...options,
2315
+ pageSize: DOCS_MCP_PROTOCOL_LIST_PAGE_SIZE
2316
+ });
2317
+ } catch (error) {
2318
+ if (error instanceof DocsPaginationCursorError) throw new McpError(ErrorCode.InvalidParams, error.message);
2319
+ throw error;
2320
+ }
2321
+ }
2322
+ function toDocsMcpProtocolPaginationMeta(result) {
2323
+ return { [DOCS_MCP_PAGINATION_META_KEY]: {
2324
+ hasMore: result.hasMore,
2325
+ total: result.total
2326
+ } };
2327
+ }
2328
+ function paginateDocsMcpSdkListResult(result, field, options) {
2329
+ const page = paginateDocsMcpProtocolItems([...result[field]].sort(options.compare), options);
2330
+ return {
2331
+ ...result,
2332
+ [field]: page.items,
2333
+ nextCursor: page.nextCursor,
2334
+ _meta: {
2335
+ ...result._meta,
2336
+ ...toDocsMcpProtocolPaginationMeta(page)
2337
+ }
2338
+ };
2339
+ }
2340
+ /**
2341
+ * The MCP SDK owns the live registries and descriptor serialization. Intercepting the
2342
+ * handlers it installs lets pagination operate on that live output without copying private
2343
+ * registry state or drifting from SDK behavior when callers register, update, or disable
2344
+ * tools/resources after this factory returns.
2345
+ */
2346
+ function installDocsMcpSdkListPaginationInterceptor(server, scope) {
2347
+ const originalSetRequestHandler = server.server.setRequestHandler;
2348
+ const setRequestHandler = originalSetRequestHandler.bind(server.server);
2349
+ const interceptedSetRequestHandler = ((requestSchema, handler) => {
2350
+ const sdkHandler = handler;
2351
+ if (Object.is(requestSchema, ListResourcesRequestSchema)) {
2352
+ setRequestHandler(ListResourcesRequestSchema, async (request, extra) => {
2353
+ return paginateDocsMcpSdkListResult(await sdkHandler(request, extra), "resources", {
2354
+ kind: "mcp.protocol/resources-list",
2355
+ scope,
2356
+ cursor: request.params?.cursor,
2357
+ compare: (left, right) => {
2358
+ const uriOrder = left.uri.localeCompare(right.uri);
2359
+ return uriOrder !== 0 ? uriOrder : left.name.localeCompare(right.name);
2360
+ }
2361
+ });
2362
+ });
2363
+ return;
2364
+ }
2365
+ if (Object.is(requestSchema, ListResourceTemplatesRequestSchema)) {
2366
+ setRequestHandler(ListResourceTemplatesRequestSchema, async (request, extra) => {
2367
+ return paginateDocsMcpSdkListResult(await sdkHandler(request, extra), "resourceTemplates", {
2368
+ kind: "mcp.protocol/resource-templates-list",
2369
+ scope,
2370
+ cursor: request.params?.cursor,
2371
+ compare: (left, right) => {
2372
+ const uriOrder = left.uriTemplate.localeCompare(right.uriTemplate);
2373
+ return uriOrder !== 0 ? uriOrder : left.name.localeCompare(right.name);
2374
+ }
2375
+ });
2376
+ });
2377
+ return;
2378
+ }
2379
+ if (Object.is(requestSchema, ListToolsRequestSchema)) {
2380
+ setRequestHandler(ListToolsRequestSchema, async (request, extra) => {
2381
+ return paginateDocsMcpSdkListResult(await sdkHandler(request, extra), "tools", {
2382
+ kind: "mcp.protocol/tools-list",
2383
+ scope,
2384
+ cursor: request.params?.cursor,
2385
+ compare: (left, right) => left.name.localeCompare(right.name)
2386
+ });
2387
+ });
2388
+ return;
2389
+ }
2390
+ if (Object.is(requestSchema, ListPromptsRequestSchema)) {
2391
+ setRequestHandler(ListPromptsRequestSchema, async (request, extra) => {
2392
+ return paginateDocsMcpSdkListResult(await sdkHandler(request, extra), "prompts", {
2393
+ kind: "mcp.protocol/prompts-list",
2394
+ scope,
2395
+ cursor: request.params?.cursor,
2396
+ compare: (left, right) => left.name.localeCompare(right.name)
2397
+ });
2398
+ });
2399
+ return;
2400
+ }
2401
+ setRequestHandler(requestSchema, handler);
2402
+ });
2403
+ server.server.setRequestHandler = interceptedSetRequestHandler;
2404
+ return () => {
2405
+ if (server.server.setRequestHandler === interceptedSetRequestHandler) server.server.setRequestHandler = originalSetRequestHandler;
2406
+ };
2407
+ }
2408
+ function installDocsMcpSdkRegistrationPagination(server, scope) {
2409
+ const wrapRegistration = (registration) => new Proxy(registration, { apply(target, thisArg, argArray) {
2410
+ const restore = installDocsMcpSdkListPaginationInterceptor(server, scope);
2411
+ try {
2412
+ return Reflect.apply(target, thisArg, argArray);
2413
+ } finally {
2414
+ restore();
2415
+ }
2416
+ } });
2417
+ server.registerTool = wrapRegistration(server.registerTool);
2418
+ server.tool = wrapRegistration(server.tool);
2419
+ server.registerResource = wrapRegistration(server.registerResource);
2420
+ server.resource = wrapRegistration(server.resource);
2421
+ server.registerPrompt = wrapRegistration(server.registerPrompt);
2422
+ server.prompt = wrapRegistration(server.prompt);
2423
+ }
2223
2424
  async function createDocsMcpServer(options) {
2224
2425
  const resolved = resolveDocsMcpConfig(options.mcp, {
2225
2426
  defaultName: options.defaultName ?? options.source.siteTitle ?? DEFAULT_MCP_NAME,
2226
2427
  defaultVersion: options.defaultVersion
2227
2428
  });
2228
2429
  const toolSearchConfig = resolveMcpToolSearchConfig(options.search, resolved.route);
2430
+ const protocolScope = stableDocsMcpPaginationScope({
2431
+ name: resolved.name,
2432
+ version: resolved.version
2433
+ });
2229
2434
  const server = new McpServer({
2230
2435
  name: resolved.name,
2231
2436
  version: resolved.version
2232
2437
  });
2438
+ installDocsMcpSdkRegistrationPagination(server, protocolScope);
2439
+ const registerResource = (name, uri, metadata, callback) => {
2440
+ return server.registerResource(name, uri, metadata, callback);
2441
+ };
2442
+ const registerTool = (name, config, callback) => {
2443
+ return server.registerTool(name, config, callback);
2444
+ };
2233
2445
  const telemetryConfig = {
2234
2446
  telemetry: options.telemetry,
2235
2447
  mcp: options.mcp,
@@ -2260,7 +2472,7 @@ async function createDocsMcpServer(options) {
2260
2472
  const defaultPages = dedupePages(await getSourcePages());
2261
2473
  const defaultTree = await getSourceNavigation();
2262
2474
  const defaultSkills = await getSourceSkills();
2263
- server.registerResource("docs-navigation", "docs://navigation", {
2475
+ registerResource("docs-navigation", "docs://navigation", {
2264
2476
  title: "Docs Navigation",
2265
2477
  description: "Structured navigation tree for the documentation site.",
2266
2478
  mimeType: "text/plain"
@@ -2271,7 +2483,7 @@ async function createDocsMcpServer(options) {
2271
2483
  }] }));
2272
2484
  for (const page of defaultPages) {
2273
2485
  const resourceUri = toPageResourceUri(page.url);
2274
- server.registerResource(`page-${slugToKey(page.slug)}`, resourceUri, {
2486
+ registerResource(`page-${slugToKey(page.slug)}`, resourceUri, {
2275
2487
  title: page.title,
2276
2488
  description: page.description,
2277
2489
  mimeType: "text/markdown"
@@ -2284,7 +2496,7 @@ async function createDocsMcpServer(options) {
2284
2496
  for (const skill of defaultSkills) for (const file of skill.files) {
2285
2497
  const encodedPath = file.path.split("/").map((segment) => encodeURIComponent(segment)).join("/");
2286
2498
  const resourceUri = `docs://skills/${encodeURIComponent(skill.name)}/${encodedPath}`;
2287
- server.registerResource(`skill-${encodeURIComponent(skill.name)}-${Buffer.from(file.path).toString("base64url")}`, resourceUri, {
2499
+ registerResource(`skill-${encodeURIComponent(skill.name)}-${Buffer.from(file.path).toString("base64url")}`, resourceUri, {
2288
2500
  title: `${skill.name}: ${file.path}`,
2289
2501
  description: `${skill.description} (${file.digest})`,
2290
2502
  mimeType: file.mediaType
@@ -2298,14 +2510,15 @@ async function createDocsMcpServer(options) {
2298
2510
  blob: Buffer.from(file.content).toString("base64")
2299
2511
  }] }));
2300
2512
  }
2301
- if (resolved.tools.listPages) server.registerTool("list_pages", {
2513
+ if (resolved.tools.listPages) registerTool("list_pages", {
2302
2514
  title: "List docs pages",
2303
2515
  description: "List the known documentation pages with titles, slugs, and URLs.",
2304
2516
  inputSchema: listPagesInputSchema,
2305
2517
  outputSchema: listPagesOutputSchema,
2306
2518
  annotations: { readOnlyHint: true }
2307
- }, async ({ locale }) => {
2519
+ }, async ({ locale, cursor }) => {
2308
2520
  const startedAt = nowMs();
2521
+ const resolvedLocale = resolveSourceLocale(locale);
2309
2522
  const trace = createDocsAgentTraceContext("mcp.tool.list_pages");
2310
2523
  const callSpanId = createDocsAgentTraceId("span");
2311
2524
  await emitDocsAgentTraceEvent(options.observability, {
@@ -2317,11 +2530,25 @@ async function createDocsMcpServer(options) {
2317
2530
  startedAt: trace.startedAt,
2318
2531
  status: "started",
2319
2532
  locale,
2320
- inputPreview: { locale },
2533
+ inputPreview: {
2534
+ locale,
2535
+ hasCursor: cursor !== void 0
2536
+ },
2321
2537
  metadata: { tool: "list_pages" }
2322
2538
  });
2323
2539
  try {
2324
- const pages = toPageSummaries(dedupePages(await getSourcePages(locale)));
2540
+ const page = paginateDocsMcpItems(toPageSummaries(dedupePages(await options.source.getPages(resolvedLocale, options.requestContext))).sort(compareDocsMcpPageSummaries), {
2541
+ kind: "mcp.tool/list_pages",
2542
+ scope: stableDocsMcpPaginationScope({ locale: resolvedLocale ?? null }),
2543
+ cursor
2544
+ });
2545
+ const result = {
2546
+ resultCount: page.resultCount,
2547
+ total: page.total,
2548
+ hasMore: page.hasMore,
2549
+ ...page.nextCursor ? { nextCursor: page.nextCursor } : {},
2550
+ pages: page.items
2551
+ };
2325
2552
  const elapsed = durationMs(startedAt);
2326
2553
  await emitDocsAnalyticsEvent(options.analytics, {
2327
2554
  type: "mcp_tool",
@@ -2329,13 +2556,15 @@ async function createDocsMcpServer(options) {
2329
2556
  locale,
2330
2557
  properties: {
2331
2558
  tool: "list_pages",
2332
- resultCount: pages.length,
2559
+ resultCount: page.resultCount,
2560
+ total: page.total,
2561
+ hasMore: page.hasMore,
2333
2562
  durationMs: elapsed
2334
2563
  }
2335
2564
  });
2336
2565
  trackMcpTool("list_pages", {
2337
2566
  locale,
2338
- resultCount: pages.length
2567
+ resultCount: page.resultCount
2339
2568
  });
2340
2569
  await emitDocsAgentTraceEvent(options.observability, {
2341
2570
  type: "tool.result",
@@ -2348,10 +2577,14 @@ async function createDocsMcpServer(options) {
2348
2577
  durationMs: elapsed,
2349
2578
  status: "success",
2350
2579
  locale,
2351
- outputPreview: { resultCount: pages.length },
2580
+ outputPreview: {
2581
+ resultCount: page.resultCount,
2582
+ total: page.total,
2583
+ hasMore: page.hasMore
2584
+ },
2352
2585
  metadata: { tool: "list_pages" }
2353
2586
  });
2354
- return createStructuredTextResult({ pages });
2587
+ return createStructuredTextResult(result);
2355
2588
  } catch (error) {
2356
2589
  const elapsed = durationMs(startedAt);
2357
2590
  await emitDocsAgentTraceEvent(options.observability, {
@@ -2371,14 +2604,15 @@ async function createDocsMcpServer(options) {
2371
2604
  throw error;
2372
2605
  }
2373
2606
  });
2374
- if (resolved.tools.listDocs) server.registerTool("list_docs", {
2607
+ if (resolved.tools.listDocs) registerTool("list_docs", {
2375
2608
  title: "List docs by section",
2376
2609
  description: "List documentation pages grouped by section, optionally narrowed to one section.",
2377
2610
  inputSchema: listDocsInputSchema,
2378
2611
  outputSchema: listDocsOutputSchema,
2379
2612
  annotations: { readOnlyHint: true }
2380
- }, async ({ section, locale }) => {
2613
+ }, async ({ section, locale, cursor }) => {
2381
2614
  const startedAt = nowMs();
2615
+ const resolvedLocale = resolveSourceLocale(locale);
2382
2616
  const trace = createDocsAgentTraceContext("mcp.tool.list_docs");
2383
2617
  const callSpanId = createDocsAgentTraceId("span");
2384
2618
  await emitDocsAgentTraceEvent(options.observability, {
@@ -2392,14 +2626,21 @@ async function createDocsMcpServer(options) {
2392
2626
  locale,
2393
2627
  inputPreview: {
2394
2628
  section,
2395
- locale
2629
+ locale,
2630
+ hasCursor: cursor !== void 0
2396
2631
  },
2397
2632
  metadata: { tool: "list_docs" }
2398
2633
  });
2399
2634
  try {
2400
- const docs = listDocsBySection(dedupePages(await getSourcePages(locale)), {
2635
+ const docs = paginateDocsMcpDocsList(listDocsBySection(dedupePages(await options.source.getPages(resolvedLocale, options.requestContext)), {
2401
2636
  section,
2402
2637
  entry: options.source.entry
2638
+ }), {
2639
+ cursor,
2640
+ scope: stableDocsMcpPaginationScope({
2641
+ locale: resolvedLocale ?? null,
2642
+ section: normalizeDocsListMatchValue(section ?? "")
2643
+ })
2403
2644
  });
2404
2645
  const elapsed = durationMs(startedAt);
2405
2646
  await emitDocsAnalyticsEvent(options.analytics, {
@@ -2410,6 +2651,8 @@ async function createDocsMcpServer(options) {
2410
2651
  tool: "list_docs",
2411
2652
  section,
2412
2653
  resultCount: docs.resultCount,
2654
+ total: docs.total,
2655
+ hasMore: docs.hasMore,
2413
2656
  sectionCount: docs.sectionCount,
2414
2657
  durationMs: elapsed
2415
2658
  }
@@ -2431,6 +2674,8 @@ async function createDocsMcpServer(options) {
2431
2674
  locale,
2432
2675
  outputPreview: {
2433
2676
  resultCount: docs.resultCount,
2677
+ total: docs.total,
2678
+ hasMore: docs.hasMore,
2434
2679
  sectionCount: docs.sectionCount
2435
2680
  },
2436
2681
  metadata: { tool: "list_docs" }
@@ -2455,14 +2700,15 @@ async function createDocsMcpServer(options) {
2455
2700
  throw error;
2456
2701
  }
2457
2702
  });
2458
- if (resolved.tools.listTasks) server.registerTool("list_tasks", {
2703
+ if (resolved.tools.listTasks) registerTool("list_tasks", {
2459
2704
  title: "List documented tasks",
2460
2705
  description: "List pages with actionable agent contracts, optionally filtered by text or applicability.",
2461
2706
  inputSchema: listTasksInputSchema,
2462
2707
  outputSchema: listTasksOutputSchema,
2463
2708
  annotations: { readOnlyHint: true }
2464
- }, async ({ query, framework, version, package: packageName, locale }) => {
2709
+ }, async ({ query, framework, version, package: packageName, locale, cursor }) => {
2465
2710
  const startedAt = nowMs();
2711
+ const resolvedLocale = resolveSourceLocale(locale);
2466
2712
  const trace = createDocsAgentTraceContext("mcp.tool.list_tasks");
2467
2713
  const callSpanId = createDocsAgentTraceId("span");
2468
2714
  await emitDocsAgentTraceEvent(options.observability, {
@@ -2478,20 +2724,34 @@ async function createDocsMcpServer(options) {
2478
2724
  queryLength: query?.length,
2479
2725
  framework,
2480
2726
  version,
2481
- package: packageName
2727
+ package: packageName,
2728
+ hasCursor: cursor !== void 0
2482
2729
  },
2483
2730
  metadata: { tool: "list_tasks" }
2484
2731
  });
2485
2732
  try {
2486
- const tasks = listDocsTasks(dedupePages(await getSourcePages(locale)), {
2733
+ const page = paginateDocsMcpItems(listDocsTasks(dedupePages(await options.source.getPages(resolvedLocale, options.requestContext)), {
2487
2734
  query,
2488
2735
  framework,
2489
2736
  version,
2490
2737
  package: packageName
2738
+ }).sort(compareDocsMcpTaskSummaries), {
2739
+ kind: "mcp.tool/list_tasks",
2740
+ scope: stableDocsMcpPaginationScope({
2741
+ locale: resolvedLocale ?? null,
2742
+ query: query?.trim().toLowerCase() ?? null,
2743
+ framework: framework?.trim().toLowerCase() ?? null,
2744
+ version: version?.trim().toLowerCase() ?? null,
2745
+ package: packageName?.trim().toLowerCase() ?? null
2746
+ }),
2747
+ cursor
2491
2748
  });
2492
2749
  const result = {
2493
- resultCount: tasks.length,
2494
- tasks
2750
+ resultCount: page.resultCount,
2751
+ total: page.total,
2752
+ hasMore: page.hasMore,
2753
+ ...page.nextCursor ? { nextCursor: page.nextCursor } : {},
2754
+ tasks: page.items
2495
2755
  };
2496
2756
  const elapsed = durationMs(startedAt);
2497
2757
  await emitDocsAnalyticsEvent(options.analytics, {
@@ -2504,13 +2764,15 @@ async function createDocsMcpServer(options) {
2504
2764
  framework,
2505
2765
  version,
2506
2766
  package: packageName,
2507
- resultCount: tasks.length,
2767
+ resultCount: page.resultCount,
2768
+ total: page.total,
2769
+ hasMore: page.hasMore,
2508
2770
  durationMs: elapsed
2509
2771
  }
2510
2772
  });
2511
2773
  trackMcpTool("list_tasks", {
2512
2774
  locale,
2513
- resultCount: tasks.length
2775
+ resultCount: page.resultCount
2514
2776
  });
2515
2777
  await emitDocsAgentTraceEvent(options.observability, {
2516
2778
  type: "tool.result",
@@ -2523,7 +2785,11 @@ async function createDocsMcpServer(options) {
2523
2785
  durationMs: elapsed,
2524
2786
  status: "success",
2525
2787
  locale,
2526
- outputPreview: { resultCount: tasks.length },
2788
+ outputPreview: {
2789
+ resultCount: page.resultCount,
2790
+ total: page.total,
2791
+ hasMore: page.hasMore
2792
+ },
2527
2793
  metadata: { tool: "list_tasks" }
2528
2794
  });
2529
2795
  return {
@@ -2552,7 +2818,7 @@ async function createDocsMcpServer(options) {
2552
2818
  throw error;
2553
2819
  }
2554
2820
  });
2555
- if (resolved.tools.readTask) server.registerTool("read_task", {
2821
+ if (resolved.tools.readTask) registerTool("read_task", {
2556
2822
  title: "Read a documented task",
2557
2823
  description: "Read the full structured agent contract for a page by slug or URL path.",
2558
2824
  inputSchema: readTaskInputSchema,
@@ -2701,7 +2967,7 @@ async function createDocsMcpServer(options) {
2701
2967
  throw error;
2702
2968
  }
2703
2969
  });
2704
- if (resolved.tools.getNavigation) server.registerTool("get_navigation", {
2970
+ if (resolved.tools.getNavigation) registerTool("get_navigation", {
2705
2971
  title: "Get docs navigation",
2706
2972
  description: "Return the documentation navigation tree for the current docs site.",
2707
2973
  inputSchema: getNavigationInputSchema,
@@ -2774,7 +3040,7 @@ async function createDocsMcpServer(options) {
2774
3040
  throw error;
2775
3041
  }
2776
3042
  });
2777
- if (resolved.tools.getConfigSchema) server.registerTool("get_config_schema", {
3043
+ if (resolved.tools.getConfigSchema) registerTool("get_config_schema", {
2778
3044
  title: "Get docs config schema",
2779
3045
  description: "Return structured docs.config.ts option metadata, optionally filtered by option path or query.",
2780
3046
  inputSchema: getConfigSchemaInputSchema,
@@ -2849,13 +3115,13 @@ async function createDocsMcpServer(options) {
2849
3115
  throw error;
2850
3116
  }
2851
3117
  });
2852
- if (resolved.tools.searchDocs) server.registerTool("search_docs", {
3118
+ if (resolved.tools.searchDocs) registerTool("search_docs", {
2853
3119
  title: "Search documentation",
2854
3120
  description: "Search the docs by keyword with optional framework, version, package, and tag filters. Returns structured ambiguity and metadata warnings.",
2855
3121
  inputSchema: searchDocsInputSchema,
2856
3122
  outputSchema: searchDocsOutputSchema,
2857
3123
  annotations: { readOnlyHint: true }
2858
- }, async ({ query, limit, locale, audience, framework, version, package: packageName, tags }) => {
3124
+ }, async ({ query, limit, cursor, locale, audience, framework, version, package: packageName, tags }) => {
2859
3125
  const startedAt = nowMs();
2860
3126
  const resolvedLimit = limit ?? 10;
2861
3127
  const resolvedAudience = audience ?? "agent";
@@ -2880,6 +3146,7 @@ async function createDocsMcpServer(options) {
2880
3146
  inputPreview: {
2881
3147
  queryLength: query.length,
2882
3148
  limit: resolvedLimit,
3149
+ hasCursor: cursor !== void 0,
2883
3150
  locale,
2884
3151
  audience: resolvedAudience,
2885
3152
  filterFields: Object.entries(filters).filter(([, value]) => value !== void 0).map(([field]) => field)
@@ -2897,7 +3164,8 @@ async function createDocsMcpServer(options) {
2897
3164
  siteTitle: options.source.siteTitle,
2898
3165
  baseUrl: options.source.baseUrl ?? (options.requestContext?.request ? new URL(options.requestContext.request.url).origin : void 0),
2899
3166
  syncBaseUrl: options.source.baseUrl ?? null,
2900
- limit: resolvedLimit
3167
+ limit: resolvedLimit,
3168
+ cursor
2901
3169
  });
2902
3170
  const elapsed = durationMs(startedAt);
2903
3171
  await emitDocsAnalyticsEvent(options.analytics, {
@@ -2911,6 +3179,8 @@ async function createDocsMcpServer(options) {
2911
3179
  limit: resolvedLimit,
2912
3180
  audience: resolvedAudience,
2913
3181
  resultCount: searchResponse.resultCount,
3182
+ total: searchResponse.total,
3183
+ hasMore: searchResponse.hasMore,
2914
3184
  warningCount: searchResponse.warnings.length,
2915
3185
  durationMs: elapsed
2916
3186
  }
@@ -2932,6 +3202,8 @@ async function createDocsMcpServer(options) {
2932
3202
  locale,
2933
3203
  outputPreview: {
2934
3204
  resultCount: searchResponse.resultCount,
3205
+ total: searchResponse.total,
3206
+ hasMore: searchResponse.hasMore,
2935
3207
  warningCount: searchResponse.warnings.length
2936
3208
  },
2937
3209
  metadata: { tool: "search_docs" }
@@ -2956,7 +3228,7 @@ async function createDocsMcpServer(options) {
2956
3228
  throw error;
2957
3229
  }
2958
3230
  });
2959
- if (resolved.tools.getCodeExamples) server.registerTool("get_code_examples", {
3231
+ if (resolved.tools.getCodeExamples) registerTool("get_code_examples", {
2960
3232
  title: "Get docs code examples",
2961
3233
  description: "Return fenced code examples from the docs with parsed metadata such as title, framework, packageManager, and runnable.",
2962
3234
  inputSchema: getCodeExamplesInputSchema,
@@ -3055,7 +3327,7 @@ async function createDocsMcpServer(options) {
3055
3327
  throw error;
3056
3328
  }
3057
3329
  });
3058
- if (resolved.tools.getContext) server.registerTool("get_context", {
3330
+ if (resolved.tools.getContext) registerTool("get_context", {
3059
3331
  title: "Get budgeted docs context",
3060
3332
  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.",
3061
3333
  inputSchema: getContextInputSchema,
@@ -3165,7 +3437,179 @@ async function createDocsMcpServer(options) {
3165
3437
  throw error;
3166
3438
  }
3167
3439
  });
3168
- if (resolved.tools.readPage) server.registerTool("read_page", {
3440
+ if (resolved.tools.listPageSections !== false) registerTool("list_page_sections", {
3441
+ title: "List sections in a docs page",
3442
+ description: "Discover a page's canonical headings, anchors, hierarchy, line ranges, size estimates, and budget-aware fetch URLs without returning the page body.",
3443
+ inputSchema: listPageSectionsInputSchema,
3444
+ outputSchema: pageSectionIndexOutputSchema,
3445
+ annotations: { readOnlyHint: true }
3446
+ }, async ({ path: requestedPath, locale, tokenBudget, byteBudget, cursor }) => {
3447
+ const startedAt = nowMs();
3448
+ const resolvedLocale = resolveSourceLocale(locale);
3449
+ const trace = createDocsAgentTraceContext("mcp.tool.list_page_sections");
3450
+ const callSpanId = createDocsAgentTraceId("span");
3451
+ await emitDocsAgentTraceEvent(options.observability, {
3452
+ type: "tool.call",
3453
+ source: "mcp",
3454
+ traceId: trace.traceId,
3455
+ spanId: callSpanId,
3456
+ name: "list_page_sections",
3457
+ startedAt: trace.startedAt,
3458
+ status: "started",
3459
+ locale: resolvedLocale,
3460
+ inputPreview: {
3461
+ path: requestedPath,
3462
+ locale,
3463
+ resolvedLocale,
3464
+ tokenBudget,
3465
+ byteBudget,
3466
+ hasCursor: cursor !== void 0
3467
+ },
3468
+ metadata: { tool: "list_page_sections" }
3469
+ });
3470
+ try {
3471
+ const page = findDocsPage(dedupePages(await options.source.getPages(resolvedLocale, options.requestContext)), requestedPath, options.source.entry);
3472
+ if (!page) {
3473
+ const elapsed = durationMs(startedAt);
3474
+ await emitDocsAnalyticsEvent(options.analytics, {
3475
+ type: "mcp_tool",
3476
+ source: "mcp",
3477
+ locale: resolvedLocale,
3478
+ properties: {
3479
+ tool: "list_page_sections",
3480
+ requestedPath,
3481
+ found: false,
3482
+ durationMs: elapsed
3483
+ }
3484
+ });
3485
+ trackMcpTool("list_page_sections", {
3486
+ locale: resolvedLocale,
3487
+ resultCount: 0
3488
+ });
3489
+ await emitDocsAgentTraceEvent(options.observability, {
3490
+ type: "tool.error",
3491
+ source: "mcp",
3492
+ traceId: trace.traceId,
3493
+ parentSpanId: callSpanId,
3494
+ name: "list_page_sections",
3495
+ startedAt: trace.startedAt,
3496
+ endedAt: (/* @__PURE__ */ new Date()).toISOString(),
3497
+ durationMs: elapsed,
3498
+ status: "error",
3499
+ locale: resolvedLocale,
3500
+ outputPreview: {
3501
+ found: false,
3502
+ path: requestedPath
3503
+ },
3504
+ metadata: {
3505
+ tool: "list_page_sections",
3506
+ reason: "not_found"
3507
+ }
3508
+ });
3509
+ return {
3510
+ content: [{
3511
+ type: "text",
3512
+ text: JSON.stringify({ error: `No docs page matched "${requestedPath}".` }, null, 2)
3513
+ }],
3514
+ isError: true
3515
+ };
3516
+ }
3517
+ const document = upsertPageAgentContractMarkdown(getDocsMcpSourceMarkdown(page), page.agent);
3518
+ const publicBaseUrl = options.source.baseUrl ?? (options.requestContext?.request ? new URL(options.requestContext.request.url).origin : void 0);
3519
+ const completeSectionIndex = buildDocsMarkdownSectionIndex(document, {
3520
+ canonicalUrl: resolveDocsMcpPublicUrl(withDocsMcpUrlLocale(page.url, resolvedLocale), publicBaseUrl),
3521
+ markdownUrl: resolveDocsMcpPublicUrl(toDocsMcpPageMarkdownUrl(page.url, resolvedLocale), publicBaseUrl),
3522
+ tokenBudget,
3523
+ byteBudget
3524
+ });
3525
+ const sectionPage = paginateDocsMcpItems(completeSectionIndex.sections, {
3526
+ kind: "mcp.tool/list_page_sections",
3527
+ scope: stableDocsMcpPaginationScope({
3528
+ path: page.url,
3529
+ locale: resolvedLocale ?? null,
3530
+ tokenBudget: tokenBudget ?? null,
3531
+ byteBudget: byteBudget ?? null
3532
+ }),
3533
+ cursor
3534
+ });
3535
+ const sectionIndex = {
3536
+ ...completeSectionIndex,
3537
+ sectionCount: completeSectionIndex.sectionCount,
3538
+ resultCount: sectionPage.resultCount,
3539
+ total: sectionPage.total,
3540
+ hasMore: sectionPage.hasMore,
3541
+ ...sectionPage.nextCursor ? { nextCursor: sectionPage.nextCursor } : {},
3542
+ sections: sectionPage.items
3543
+ };
3544
+ const elapsed = durationMs(startedAt);
3545
+ await emitDocsAnalyticsEvent(options.analytics, {
3546
+ type: "mcp_tool",
3547
+ source: "mcp",
3548
+ locale: resolvedLocale,
3549
+ path: page.url,
3550
+ properties: {
3551
+ tool: "list_page_sections",
3552
+ requestedPath,
3553
+ slug: page.slug,
3554
+ found: true,
3555
+ sectionCount: sectionIndex.sectionCount,
3556
+ total: sectionIndex.total,
3557
+ hasMore: sectionIndex.hasMore,
3558
+ estimatedTokens: sectionIndex.estimatedTokens,
3559
+ utf8Bytes: sectionIndex.utf8Bytes,
3560
+ tokenBudget,
3561
+ byteBudget,
3562
+ durationMs: elapsed
3563
+ }
3564
+ });
3565
+ trackMcpTool("list_page_sections", {
3566
+ locale: resolvedLocale,
3567
+ resultCount: sectionIndex.resultCount
3568
+ });
3569
+ await emitDocsAgentTraceEvent(options.observability, {
3570
+ type: "tool.result",
3571
+ source: "mcp",
3572
+ traceId: trace.traceId,
3573
+ parentSpanId: callSpanId,
3574
+ name: "list_page_sections",
3575
+ startedAt: trace.startedAt,
3576
+ endedAt: (/* @__PURE__ */ new Date()).toISOString(),
3577
+ durationMs: elapsed,
3578
+ status: "success",
3579
+ locale: resolvedLocale,
3580
+ path: page.url,
3581
+ outputPreview: {
3582
+ found: true,
3583
+ slug: page.slug,
3584
+ sectionCount: sectionIndex.sectionCount,
3585
+ total: sectionIndex.total,
3586
+ hasMore: sectionIndex.hasMore,
3587
+ estimatedTokens: sectionIndex.estimatedTokens,
3588
+ utf8Bytes: sectionIndex.utf8Bytes
3589
+ },
3590
+ metadata: { tool: "list_page_sections" }
3591
+ });
3592
+ return createStructuredTextResult(sectionIndex);
3593
+ } catch (error) {
3594
+ const elapsed = durationMs(startedAt);
3595
+ await emitDocsAgentTraceEvent(options.observability, {
3596
+ type: "tool.error",
3597
+ source: "mcp",
3598
+ traceId: trace.traceId,
3599
+ parentSpanId: callSpanId,
3600
+ name: "list_page_sections",
3601
+ startedAt: trace.startedAt,
3602
+ endedAt: (/* @__PURE__ */ new Date()).toISOString(),
3603
+ durationMs: elapsed,
3604
+ status: "error",
3605
+ locale: resolvedLocale,
3606
+ outputPreview: { message: error instanceof Error ? error.message : "Unknown error" },
3607
+ metadata: { tool: "list_page_sections" }
3608
+ });
3609
+ throw error;
3610
+ }
3611
+ });
3612
+ if (resolved.tools.readPage) registerTool("read_page", {
3169
3613
  title: "Read a docs page",
3170
3614
  description: "Read a documentation page by slug or URL path, optionally selecting one heading and limiting returned characters.",
3171
3615
  inputSchema: readPageInputSchema,
@@ -4180,6 +4624,18 @@ function toDocsListPageSummary(page) {
4180
4624
  lastModified: page.lastModified
4181
4625
  };
4182
4626
  }
4627
+ function compareDocsMcpPageSummaries(left, right) {
4628
+ const urlOrder = left.url.localeCompare(right.url);
4629
+ if (urlOrder !== 0) return urlOrder;
4630
+ const slugOrder = left.slug.localeCompare(right.slug);
4631
+ return slugOrder !== 0 ? slugOrder : left.title.localeCompare(right.title);
4632
+ }
4633
+ function compareDocsMcpTaskSummaries(left, right) {
4634
+ const urlOrder = left.url.localeCompare(right.url);
4635
+ if (urlOrder !== 0) return urlOrder;
4636
+ const slugOrder = left.slug.localeCompare(right.slug);
4637
+ return slugOrder !== 0 ? slugOrder : left.title.localeCompare(right.title);
4638
+ }
4183
4639
  function listDocsTasks(pages, filters) {
4184
4640
  const query = filters.query?.toLowerCase();
4185
4641
  const applicabilityFilters = [
@@ -4221,6 +4677,8 @@ function listDocsBySection(pages, filters) {
4221
4677
  const requestedSection = filters.section?.trim();
4222
4678
  if (!requestedSection) return {
4223
4679
  resultCount: allPages.length,
4680
+ total: allPages.length,
4681
+ hasMore: false,
4224
4682
  sectionCount: countDocsSections(tree.sections),
4225
4683
  pages: allPages,
4226
4684
  rootPages: tree.rootPages,
@@ -4233,6 +4691,8 @@ function listDocsBySection(pages, filters) {
4233
4691
  return {
4234
4692
  section: requestedSection,
4235
4693
  resultCount: matchedPages.length,
4694
+ total: matchedPages.length,
4695
+ hasMore: false,
4236
4696
  sectionCount: countDocsSections(sections),
4237
4697
  pages: matchedPages,
4238
4698
  rootPages: [],
@@ -4243,6 +4703,8 @@ function listDocsBySection(pages, filters) {
4243
4703
  if (page) return {
4244
4704
  section: requestedSection,
4245
4705
  resultCount: 1,
4706
+ total: 1,
4707
+ hasMore: false,
4246
4708
  sectionCount: 0,
4247
4709
  pages: [page],
4248
4710
  rootPages: [page],
@@ -4251,12 +4713,46 @@ function listDocsBySection(pages, filters) {
4251
4713
  return {
4252
4714
  section: requestedSection,
4253
4715
  resultCount: 0,
4716
+ total: 0,
4717
+ hasMore: false,
4254
4718
  sectionCount: 0,
4255
4719
  pages: [],
4256
4720
  rootPages: [],
4257
4721
  sections: []
4258
4722
  };
4259
4723
  }
4724
+ function paginateDocsMcpDocsList(docs, options) {
4725
+ const page = paginateDocsMcpItems([...docs.pages].sort(compareDocsMcpPageSummaries), {
4726
+ kind: "mcp.tool/list_docs",
4727
+ scope: options.scope,
4728
+ cursor: options.cursor
4729
+ });
4730
+ const pageUrls = new Set(page.items.map((item) => item.url));
4731
+ const sections = pruneDocsMcpSections(docs.sections, pageUrls);
4732
+ return {
4733
+ ...docs,
4734
+ resultCount: page.resultCount,
4735
+ total: page.total,
4736
+ hasMore: page.hasMore,
4737
+ ...page.nextCursor ? { nextCursor: page.nextCursor } : {},
4738
+ pages: page.items,
4739
+ rootPages: docs.rootPages.filter((item) => pageUrls.has(item.url)),
4740
+ sections,
4741
+ sectionCount: docs.sectionCount
4742
+ };
4743
+ }
4744
+ function pruneDocsMcpSections(sections, pageUrls) {
4745
+ return sections.flatMap((section) => {
4746
+ const pages = section.pages.filter((page) => pageUrls.has(page.url));
4747
+ const childSections = pruneDocsMcpSections(section.sections, pageUrls);
4748
+ if (pages.length === 0 && childSections.length === 0) return [];
4749
+ return [{
4750
+ ...section,
4751
+ pages,
4752
+ sections: childSections
4753
+ }];
4754
+ });
4755
+ }
4260
4756
  function buildDocsSectionTree(pages) {
4261
4757
  const sectionSlugs = /* @__PURE__ */ new Set();
4262
4758
  for (const page of pages) {
@@ -4611,6 +5107,35 @@ function toStructuredDocsMcpPage(page) {
4611
5107
  tags: page.tags
4612
5108
  };
4613
5109
  }
5110
+ function withDocsMcpUrlLocale(value, locale) {
5111
+ if (!locale) return value;
5112
+ const absolute = /^[a-z][a-z\d+.-]*:/iu.test(value);
5113
+ try {
5114
+ const url = new URL(value, "https://docs.invalid");
5115
+ if (!url.searchParams.has("lang")) url.searchParams.set("lang", locale);
5116
+ return absolute ? url.toString() : `${url.pathname}${url.search}${url.hash}`;
5117
+ } catch {
5118
+ return value;
5119
+ }
5120
+ }
5121
+ function toDocsMcpPageMarkdownUrl(value, locale) {
5122
+ const absolute = /^[a-z][a-z\d+.-]*:/iu.test(value);
5123
+ try {
5124
+ const url = new URL(value, "https://docs.invalid");
5125
+ const markdownPath = toDocsMarkdownUrl(`${url.pathname}${url.search}${url.hash}`, { locale });
5126
+ return absolute ? new URL(markdownPath, url.origin).toString() : markdownPath;
5127
+ } catch {
5128
+ return toDocsMarkdownUrl(value, { locale });
5129
+ }
5130
+ }
5131
+ function resolveDocsMcpPublicUrl(value, baseUrl) {
5132
+ if (!baseUrl || /^[a-z][a-z\d+.-]*:/iu.test(value)) return value;
5133
+ try {
5134
+ return new URL(value, `${baseUrl.replace(/\/+$/u, "")}/`).toString();
5135
+ } catch {
5136
+ return value;
5137
+ }
5138
+ }
4614
5139
  function getDocsMcpSourceMarkdown(page) {
4615
5140
  return page.agentRawContent ?? page.agentFallbackRawContent ?? page.agentContent ?? page.agentFallbackContent ?? page.rawContent ?? page.content;
4616
5141
  }
@@ -4816,24 +5341,24 @@ async function buildDocsMcpContext(options) {
4816
5341
  };
4817
5342
  }
4818
5343
  function findDocsPage(pages, requestedPath, entry) {
4819
- const normalizedRequest = normalizeRequestedPath(requestedPath, entry);
4820
- const normalizedRequestWithoutLocale = normalizeRequestedPath(removeDocsLocaleQuery(requestedPath), entry);
4821
- for (const page of pages) {
4822
- const normalizedPageUrl = normalizeUrlPath(page.url);
4823
- if (normalizedPageUrl === normalizedRequest || normalizedPageUrl === normalizedRequestWithoutLocale) return page;
4824
- }
4825
- const normalizedSlug = normalizePathSegment(requestedPath.replace(/^\//, ""));
4826
- for (const page of pages) if (normalizePathSegment(page.slug) === normalizedSlug) return page;
4827
- return null;
5344
+ const normalizedRequest = normalizeDocsMcpPageIdentity(requestedPath, entry, true);
5345
+ for (const page of pages) if (normalizeDocsMcpPageIdentity(page.url, entry, true) === normalizedRequest) return page;
5346
+ const normalizedRequestPath = normalizeDocsMcpPageIdentity(requestedPath, entry, false);
5347
+ const pathMatches = pages.filter((page) => normalizeDocsMcpPageIdentity(page.url, entry, false) === normalizedRequestPath);
5348
+ if (pathMatches.length === 1) return pathMatches[0];
5349
+ if (pathMatches.length > 1) return null;
5350
+ const normalizedSlug = normalizePathSegment(normalizeDocsMcpPageIdentity(requestedPath, void 0, false).replace(/^\//u, ""));
5351
+ const slugMatches = pages.filter((page) => normalizePathSegment(page.slug) === normalizedSlug);
5352
+ return slugMatches.length === 1 ? slugMatches[0] : null;
4828
5353
  }
4829
- function removeDocsLocaleQuery(value) {
5354
+ function normalizeDocsMcpPageIdentity(value, entry, includeSearch) {
4830
5355
  try {
4831
- const absolute = /^[a-z][a-z\d+.-]*:/iu.test(value);
4832
5356
  const url = new URL(value, "https://docs.local");
4833
- url.searchParams.delete("lang");
4834
- return absolute ? url.toString() : `${url.pathname}${url.search}${url.hash}`;
5357
+ if (includeSearch) url.searchParams.sort();
5358
+ return `${normalizeRequestedPath(url.pathname, entry)}${includeSearch ? url.search : ""}`;
4835
5359
  } catch {
4836
- return value;
5360
+ const [pathname = value, search = ""] = value.split("?", 2);
5361
+ return `${normalizeRequestedPath(pathname, entry)}${includeSearch && search ? `?${search}` : ""}`;
4837
5362
  }
4838
5363
  }
4839
5364
  function normalizeRequestedPath(requestedPath, entry) {