@farming-labs/docs 0.2.77 → 0.2.79

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 (36) hide show
  1. package/dist/{agent-DRKj42ht.d.mts → agent-C3Y011P9.d.mts} +282 -15
  2. package/dist/{agent-D4hNGatV.mjs → agent-DYPnd8-9.mjs} +2 -2
  3. package/dist/{agent-SRIJ-oF8.mjs → agent-DejGcJfb.mjs} +4286 -334
  4. package/dist/{agent-evals-DmFoKy_y.mjs → agent-evals-DQbsw5fY.mjs} +1 -1
  5. package/dist/{agent-export-CM-p9_YD.mjs → agent-export-DyDl_mmh.mjs} +6 -5
  6. package/dist/agent-provenance-DEyAr22i.mjs +463 -0
  7. package/dist/agent-skills-bundle.d.mts +1 -1
  8. package/dist/{agent-skills-server-CJ4D0G9P.d.mts → agent-skills-server-Daq-FOay.d.mts} +2 -2
  9. package/dist/agent-skills-vite.d.mts +3 -3
  10. package/dist/{agents-BeLDNCnE.mjs → agents-ktSbqsOA.mjs} +4 -4
  11. package/dist/cli/index.mjs +14 -14
  12. package/dist/client/react.d.mts +1 -1
  13. package/dist/{cloud-ask-ai-BCgHPxew.d.mts → cloud-ask-ai-o41WfOTk.d.mts} +1 -1
  14. package/dist/docs-cloud-server.d.mts +2 -2
  15. package/dist/{doctor-DhMVKHjp.mjs → doctor-DPj4Af0T.mjs} +9 -9
  16. package/dist/{golden-evaluations-VzE6juQl.mjs → golden-evaluations-xjthrnDO.mjs} +7 -2
  17. package/dist/index.d.mts +5 -5
  18. package/dist/index.mjs +5 -5
  19. package/dist/{mcp-CZkQJXtA.mjs → mcp-DOisfwWF.mjs} +5 -4
  20. package/dist/mcp.d.mts +19 -6
  21. package/dist/mcp.mjs +482 -40
  22. package/dist/{prompt-references-B6L3AD98.mjs → prompt-references-DbmH5FA9.mjs} +2 -2
  23. package/dist/retrieval-digest-DIbiu0TW.d.mts +190 -0
  24. package/dist/{review-nxoV8jIm.mjs → review-X1qVmw9I.mjs} +4 -4
  25. package/dist/{robots-CiQT8mX2.mjs → robots-DTo_uqa9.mjs} +2 -2
  26. package/dist/{robots-CgH6Stqn.mjs → robots-DUdUIBpi.mjs} +1 -1
  27. package/dist/{search-BTUIkhnT.mjs → search-DMOwAmxo.mjs} +4 -4
  28. package/dist/server.d.mts +7 -7
  29. package/dist/server.mjs +6 -6
  30. package/dist/{sitemap-e0_GeCWJ.mjs → sitemap-5Lajt0xS.mjs} +4 -4
  31. package/dist/{sitemap-server-DHaxyPAd.mjs → sitemap-server-p4wkFm9-.mjs} +1 -1
  32. package/dist/{standards-discovery-BbslY4id.d.mts → standards-discovery-C2cUIQzW.d.mts} +1 -1
  33. package/dist/{types-B-5Kzmkn.d.mts → types-o-ULHPK6.d.mts} +127 -4
  34. package/package.json +6 -2
  35. package/dist/retrieval-digest-rponR4jM.d.mts +0 -332
  36. package/dist/search-Da2AKaFJ.mjs +0 -3992
package/dist/mcp.mjs CHANGED
@@ -1,15 +1,13 @@
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-SRIJ-oF8.mjs";
2
+ import { $n as resolveDocsMcpProtectedResourceMetadataLocation, An as normalizeAgentLocale, Dn as agentVersionConstraintMatches, E as DOCS_MARKDOWN_SECTION_INDEX_FORMAT, In as isDocsRetrievalCanonicalUrl, Jn as isDocsMcpProtectedResourceMetadataPath, Jt as resolveDocsContentChangesConfig, Kt as isDocsContentChangeGeneration, Ln as normalizeDocsRelated, Nn as DocsPaginationCursorError, On as agentVersionConstraintsOverlap, P as buildDocsMarkdownSectionIndex, Pn as paginateDocsItems, Qn as normalizeDocsMcpEndpointPath, Rn as renderDocsRelatedMarkdownLines, Tn as resolveDocsAudienceMdxContent, Ut as DocsContentChangesRequestError, Wt as createDocsContentChangeFeed, Xn as isDocsMcpResourcePath, Zn as normalizeDocsMcpAuthorizationServerUrls, dn as performDocsSearch, er as resolveDocsMcpResourceLocation, fn as performDocsSearchWithMetadata, jn as normalizeAgentScopeValues, kn as normalizeAgentFramework, qn as isDocsMcpOAuthScopeToken, tn as buildDocsSearchFacets, un as normalizeDocsSearchFilters, wt as toDocsMarkdownUrl } from "./agent-DejGcJfb.mjs";
3
3
  import { S as upsertPageAgentContractMarkdown, _ as hasStructuredPageAgentContract, o as findDocsMarkdownSection, s as parseDocsMarkdownSections, v as normalizePageAgentFrontmatter } from "./markdown-sections-7OoA7ylx.mjs";
4
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";
5
+ import { f as emitDocsTelemetryAgentSurfaceEvent, h as emitDocsTelemetryProjectEvent, l as resolvePageSidebarFolderIndexBehavior, m as emitDocsTelemetryMcpToolEvent, s as stripGeneratedAgentProvenance } from "./agent-provenance-DEyAr22i.mjs";
6
6
  import matter from "gray-matter";
7
7
  import fs from "node:fs";
8
8
  import path from "node:path";
9
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
11
- import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
12
- import { ErrorCode, ListPromptsRequestSchema, ListResourceTemplatesRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, McpError, isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
9
+ import { serveStdio } from "@modelcontextprotocol/server/stdio";
10
+ import { InMemoryServerEventBus, McpServer, ProtocolError, ProtocolErrorCode, ResourceTemplate, createMcpHandler, isInitializeRequest } from "@modelcontextprotocol/server";
13
11
  import * as z from "zod/v4";
14
12
 
15
13
  //#region src/mcp.ts
@@ -21,6 +19,9 @@ const MAX_MCP_CONTEXT_TOKEN_BUDGET = 32e3;
21
19
  const DOCS_MCP_PROTOCOL_LIST_PAGE_SIZE = 10;
22
20
  const DOCS_MCP_TOOL_LIST_PAGE_SIZE = 25;
23
21
  const DOCS_MCP_PAGINATION_META_KEY = "dev.farming-labs/pagination";
22
+ const DOCS_MCP_CONTENT_CHANGES_CURRENT_URI = "docs://changes/current";
23
+ const DOCS_MCP_CONTENT_CHANGES_URI_TEMPLATE = "docs://changes/{generation}";
24
+ const DEFAULT_DOCS_MCP_CONTENT_CHANGE_POLL_INTERVAL_MS = 3e4;
24
25
  const UTF8_ENCODER = new TextEncoder();
25
26
  const DEFAULT_DOCS_MCP_MAX_BODY_BYTES = 1024 * 1024;
26
27
  const DEFAULT_DOCS_MCP_CORS_MAX_AGE_SECONDS = 600;
@@ -29,6 +30,8 @@ const DEFAULT_DOCS_MCP_CORS_ALLOWED_HEADERS = Object.freeze([
29
30
  "Authorization",
30
31
  "Content-Type",
31
32
  "Last-Event-ID",
33
+ "MCP-Method",
34
+ "MCP-Name",
32
35
  "MCP-Protocol-Version",
33
36
  "MCP-Session-Id"
34
37
  ]);
@@ -223,9 +226,44 @@ const DOCS_CONFIG_SCHEMA_OPTIONS_TEMPLATE = [
223
226
  path: "agent",
224
227
  name: "agent",
225
228
  type: "DocsAgentConfig",
226
- description: "Agent compaction defaults and offline-by-default usefulness evaluations.",
229
+ description: "Agent synchronization, reusable skills, compaction defaults, and offline-by-default usefulness evaluations.",
227
230
  docs: "/docs/getting-started/agent-ready-docs",
228
231
  children: [
232
+ {
233
+ path: "agent.contentChanges",
234
+ name: "contentChanges",
235
+ type: "boolean | DocsAgentContentChangesConfig",
236
+ default: true,
237
+ description: "Body-free runtime document synchronization feed. Disable it with false, or configure bounded and durable snapshot history.",
238
+ children: [
239
+ {
240
+ path: "agent.contentChanges.enabled",
241
+ name: "enabled",
242
+ type: "boolean",
243
+ default: true,
244
+ description: "Whether runtime adapters serve response=changes."
245
+ },
246
+ {
247
+ path: "agent.contentChanges.maxSnapshots",
248
+ name: "maxSnapshots",
249
+ type: "number",
250
+ default: 8,
251
+ description: "Metadata-only snapshots retained in this server process; accepted range is 1 through 64."
252
+ },
253
+ {
254
+ path: "agent.contentChanges.loadSnapshot",
255
+ name: "loadSnapshot",
256
+ type: "DocsContentChangeSnapshotLoader",
257
+ description: "Optional callback that loads a prior generation from durable storage for exact cross-deployment deltas."
258
+ },
259
+ {
260
+ path: "agent.contentChanges.saveSnapshot",
261
+ name: "saveSnapshot",
262
+ type: "DocsContentChangeSnapshotSaver",
263
+ description: "Optional callback that persists each current metadata snapshot to durable storage."
264
+ }
265
+ ]
266
+ },
229
267
  {
230
268
  path: "agent.skills",
231
269
  name: "skills",
@@ -1526,6 +1564,20 @@ const DOCS_CONFIG_SCHEMA_OPTIONS_TEMPLATE = [
1526
1564
  default: true,
1527
1565
  description: "Expose the search_docs tool."
1528
1566
  },
1567
+ {
1568
+ path: "mcp.tools.searchFacets",
1569
+ name: "searchFacets",
1570
+ type: "boolean",
1571
+ default: true,
1572
+ description: "Expose the list_search_facets tool for body-free framework, version, package, and tag discovery."
1573
+ },
1574
+ {
1575
+ path: "mcp.tools.listContentChanges",
1576
+ name: "listContentChanges",
1577
+ type: "boolean",
1578
+ default: true,
1579
+ description: "Expose list_content_changes polling and docs://changes resources when agent.contentChanges is enabled."
1580
+ },
1529
1581
  {
1530
1582
  path: "mcp.tools.readPage",
1531
1583
  name: "readPage",
@@ -1728,6 +1780,22 @@ const searchDocsInputSchema = z.object({
1728
1780
  package: searchFilterValueSchema.optional(),
1729
1781
  tags: searchFilterValueSchema.optional()
1730
1782
  });
1783
+ const searchFacetsInputSchema = z.object({
1784
+ locale: z.string().trim().min(1).max(128).optional(),
1785
+ audience: z.enum(["human", "agent"]).optional(),
1786
+ facet: z.enum([
1787
+ "framework",
1788
+ "version",
1789
+ "package",
1790
+ "tags"
1791
+ ]).optional(),
1792
+ limit: z.number().int().min(1).max(100).optional(),
1793
+ cursor: paginationCursorInputSchema,
1794
+ framework: searchFilterValueSchema.optional(),
1795
+ version: searchFilterValueSchema.optional(),
1796
+ package: searchFilterValueSchema.optional(),
1797
+ tags: searchFilterValueSchema.optional()
1798
+ });
1731
1799
  const readPageInputSchema = z.object({
1732
1800
  path: z.string().min(1),
1733
1801
  locale: z.string().trim().min(1).max(128).optional(),
@@ -1822,6 +1890,10 @@ const listDocsInputSchema = z.object({
1822
1890
  cursor: paginationCursorInputSchema
1823
1891
  });
1824
1892
  const getNavigationInputSchema = z.object({ locale: z.string().trim().min(1).max(128).optional() });
1893
+ const listContentChangesInputSchema = z.object({
1894
+ since: z.string().refine(isDocsContentChangeGeneration, "Expected a SHA-256 index generation").optional(),
1895
+ locale: z.string().trim().min(1).max(128).optional()
1896
+ });
1825
1897
  const getConfigSchemaInputSchema = z.object({
1826
1898
  option: z.string().trim().min(1).optional(),
1827
1899
  query: z.string().trim().min(1).optional()
@@ -1929,6 +2001,37 @@ const retrievalSourceScopeOutputSchema = z.object({
1929
2001
  ])).max(4).optional()
1930
2002
  });
1931
2003
  const retrievalSourceDigestOutputSchema = z.string().regex(/^sha256:[a-f\d]{64}$/iu);
2004
+ const contentChangeDocumentOutputSchema = z.object({
2005
+ url: z.string(),
2006
+ canonicalUrl: z.string().max(4096).refine(isDocsRetrievalCanonicalUrl, "Expected a safe HTTP(S) URL or root-relative path"),
2007
+ digest: retrievalSourceDigestOutputSchema,
2008
+ lastModified: z.string().optional()
2009
+ });
2010
+ const contentChangesOutputSchema = z.object({
2011
+ format: z.literal("docs-content-changes.v1"),
2012
+ audience: z.enum(["human", "agent"]),
2013
+ locale: z.string().optional(),
2014
+ since: retrievalSourceDigestOutputSchema.nullable(),
2015
+ indexGeneration: retrievalSourceDigestOutputSchema,
2016
+ mode: z.enum([
2017
+ "snapshot",
2018
+ "delta",
2019
+ "reset"
2020
+ ]),
2021
+ resetRequired: z.boolean(),
2022
+ documentCount: z.number().int().nonnegative(),
2023
+ counts: z.object({
2024
+ added: z.number().int().nonnegative(),
2025
+ changed: z.number().int().nonnegative(),
2026
+ deleted: z.number().int().nonnegative()
2027
+ }),
2028
+ added: z.array(contentChangeDocumentOutputSchema),
2029
+ changed: z.array(contentChangeDocumentOutputSchema.extend({
2030
+ previousDigest: retrievalSourceDigestOutputSchema,
2031
+ previousLastModified: z.string().optional()
2032
+ })),
2033
+ deleted: z.array(contentChangeDocumentOutputSchema)
2034
+ });
1932
2035
  const retrievalSourceOutputSchema = z.object({
1933
2036
  canonicalUrl: z.string().max(4096).refine(isDocsRetrievalCanonicalUrl, "Expected a safe HTTP(S) URL or root-relative path"),
1934
2037
  scope: retrievalSourceScopeOutputSchema,
@@ -1984,6 +2087,30 @@ const searchDocsOutputSchema = z.object({
1984
2087
  results: z.array(searchResultOutputSchema),
1985
2088
  warnings: z.array(searchWarningOutputSchema)
1986
2089
  });
2090
+ const searchFacetOutputSchema = z.object({
2091
+ valueCount: z.number().int().nonnegative(),
2092
+ total: z.number().int().nonnegative(),
2093
+ hasMore: z.boolean(),
2094
+ nextCursor: z.string().optional(),
2095
+ truncated: z.boolean(),
2096
+ values: z.array(z.object({
2097
+ value: z.string(),
2098
+ count: z.number().int().nonnegative()
2099
+ }))
2100
+ });
2101
+ const searchFacetsOutputSchema = z.object({
2102
+ format: z.literal("docs-search-facets.v1"),
2103
+ audience: z.enum(["human", "agent"]),
2104
+ filters: searchFiltersOutputSchema,
2105
+ indexGeneration: retrievalSourceDigestOutputSchema,
2106
+ matchedPageCount: z.number().int().nonnegative(),
2107
+ facets: z.object({
2108
+ framework: searchFacetOutputSchema,
2109
+ version: searchFacetOutputSchema,
2110
+ package: searchFacetOutputSchema,
2111
+ tags: searchFacetOutputSchema
2112
+ })
2113
+ });
1987
2114
  const codeExampleOutputSchema = z.object({
1988
2115
  id: z.string(),
1989
2116
  page: z.object({
@@ -2145,6 +2272,8 @@ function resolveDocsMcpConfig(mcp, defaults = {}) {
2145
2272
  listTasks: true,
2146
2273
  readTask: true,
2147
2274
  searchDocs: true,
2275
+ searchFacets: true,
2276
+ listContentChanges: true,
2148
2277
  getNavigation: true,
2149
2278
  getCodeExamples: true,
2150
2279
  getConfigSchema: true,
@@ -2166,6 +2295,8 @@ function resolveDocsMcpConfig(mcp, defaults = {}) {
2166
2295
  listTasks: config.tools?.listTasks ?? true,
2167
2296
  readTask: config.tools?.readTask ?? true,
2168
2297
  searchDocs: config.tools?.searchDocs ?? true,
2298
+ searchFacets: config.tools?.searchFacets ?? true,
2299
+ listContentChanges: config.tools?.listContentChanges ?? true,
2169
2300
  getNavigation: config.tools?.getNavigation ?? true,
2170
2301
  getCodeExamples: config.tools?.getCodeExamples ?? true,
2171
2302
  getConfigSchema: config.tools?.getConfigSchema ?? true,
@@ -2284,6 +2415,54 @@ function nowMs() {
2284
2415
  function durationMs(startedAt) {
2285
2416
  return Math.max(0, Date.now() - startedAt);
2286
2417
  }
2418
+ function createDocsMcpContentChangeMonitor(options) {
2419
+ let context;
2420
+ let generation;
2421
+ let timer;
2422
+ let running = false;
2423
+ let closed = false;
2424
+ const schedule = () => {
2425
+ if (closed || timer || running || options.isActive?.() === false) return;
2426
+ timer = setTimeout(async () => {
2427
+ timer = void 0;
2428
+ if (closed || context === void 0 || options.isActive?.() === false) return;
2429
+ running = true;
2430
+ try {
2431
+ const nextGeneration = await options.readGeneration(context);
2432
+ if (generation && nextGeneration !== generation) {
2433
+ generation = nextGeneration;
2434
+ await options.notify();
2435
+ } else generation = nextGeneration;
2436
+ } catch {} finally {
2437
+ running = false;
2438
+ schedule();
2439
+ }
2440
+ }, options.pollIntervalMs);
2441
+ if (options.unrefTimer) timer.unref?.();
2442
+ };
2443
+ return {
2444
+ async start(nextContext) {
2445
+ if (closed) return;
2446
+ context = nextContext;
2447
+ if (timer) {
2448
+ clearTimeout(timer);
2449
+ timer = void 0;
2450
+ }
2451
+ try {
2452
+ generation = await options.readGeneration(nextContext);
2453
+ } catch {
2454
+ generation = void 0;
2455
+ }
2456
+ schedule();
2457
+ },
2458
+ close() {
2459
+ closed = true;
2460
+ context = void 0;
2461
+ if (timer) clearTimeout(timer);
2462
+ timer = void 0;
2463
+ }
2464
+ };
2465
+ }
2287
2466
  function createStructuredTextResult(structuredContent, text) {
2288
2467
  return {
2289
2468
  content: [{
@@ -2315,7 +2494,7 @@ function paginateDocsMcpProtocolItems(items, options) {
2315
2494
  pageSize: DOCS_MCP_PROTOCOL_LIST_PAGE_SIZE
2316
2495
  });
2317
2496
  } catch (error) {
2318
- if (error instanceof DocsPaginationCursorError) throw new McpError(ErrorCode.InvalidParams, error.message);
2497
+ if (error instanceof DocsPaginationCursorError) throw new ProtocolError(ProtocolErrorCode.InvalidParams, error.message);
2319
2498
  throw error;
2320
2499
  }
2321
2500
  }
@@ -2346,10 +2525,10 @@ function paginateDocsMcpSdkListResult(result, field, options) {
2346
2525
  function installDocsMcpSdkListPaginationInterceptor(server, scope) {
2347
2526
  const originalSetRequestHandler = server.server.setRequestHandler;
2348
2527
  const setRequestHandler = originalSetRequestHandler.bind(server.server);
2349
- const interceptedSetRequestHandler = ((requestSchema, handler) => {
2528
+ const interceptedSetRequestHandler = ((method, handler) => {
2350
2529
  const sdkHandler = handler;
2351
- if (Object.is(requestSchema, ListResourcesRequestSchema)) {
2352
- setRequestHandler(ListResourcesRequestSchema, async (request, extra) => {
2530
+ if (method === "resources/list") {
2531
+ setRequestHandler("resources/list", async (request, extra) => {
2353
2532
  return paginateDocsMcpSdkListResult(await sdkHandler(request, extra), "resources", {
2354
2533
  kind: "mcp.protocol/resources-list",
2355
2534
  scope,
@@ -2362,8 +2541,8 @@ function installDocsMcpSdkListPaginationInterceptor(server, scope) {
2362
2541
  });
2363
2542
  return;
2364
2543
  }
2365
- if (Object.is(requestSchema, ListResourceTemplatesRequestSchema)) {
2366
- setRequestHandler(ListResourceTemplatesRequestSchema, async (request, extra) => {
2544
+ if (method === "resources/templates/list") {
2545
+ setRequestHandler("resources/templates/list", async (request, extra) => {
2367
2546
  return paginateDocsMcpSdkListResult(await sdkHandler(request, extra), "resourceTemplates", {
2368
2547
  kind: "mcp.protocol/resource-templates-list",
2369
2548
  scope,
@@ -2376,8 +2555,8 @@ function installDocsMcpSdkListPaginationInterceptor(server, scope) {
2376
2555
  });
2377
2556
  return;
2378
2557
  }
2379
- if (Object.is(requestSchema, ListToolsRequestSchema)) {
2380
- setRequestHandler(ListToolsRequestSchema, async (request, extra) => {
2558
+ if (method === "tools/list") {
2559
+ setRequestHandler("tools/list", async (request, extra) => {
2381
2560
  return paginateDocsMcpSdkListResult(await sdkHandler(request, extra), "tools", {
2382
2561
  kind: "mcp.protocol/tools-list",
2383
2562
  scope,
@@ -2387,8 +2566,8 @@ function installDocsMcpSdkListPaginationInterceptor(server, scope) {
2387
2566
  });
2388
2567
  return;
2389
2568
  }
2390
- if (Object.is(requestSchema, ListPromptsRequestSchema)) {
2391
- setRequestHandler(ListPromptsRequestSchema, async (request, extra) => {
2569
+ if (method === "prompts/list") {
2570
+ setRequestHandler("prompts/list", async (request, extra) => {
2392
2571
  return paginateDocsMcpSdkListResult(await sdkHandler(request, extra), "prompts", {
2393
2572
  kind: "mcp.protocol/prompts-list",
2394
2573
  scope,
@@ -2398,7 +2577,7 @@ function installDocsMcpSdkListPaginationInterceptor(server, scope) {
2398
2577
  });
2399
2578
  return;
2400
2579
  }
2401
- setRequestHandler(requestSchema, handler);
2580
+ setRequestHandler(method, handler);
2402
2581
  });
2403
2582
  server.server.setRequestHandler = interceptedSetRequestHandler;
2404
2583
  return () => {
@@ -2415,11 +2594,8 @@ function installDocsMcpSdkRegistrationPagination(server, scope) {
2415
2594
  }
2416
2595
  } });
2417
2596
  server.registerTool = wrapRegistration(server.registerTool);
2418
- server.tool = wrapRegistration(server.tool);
2419
2597
  server.registerResource = wrapRegistration(server.registerResource);
2420
- server.resource = wrapRegistration(server.resource);
2421
2598
  server.registerPrompt = wrapRegistration(server.registerPrompt);
2422
- server.prompt = wrapRegistration(server.prompt);
2423
2599
  }
2424
2600
  async function createDocsMcpServer(options) {
2425
2601
  const resolved = resolveDocsMcpConfig(options.mcp, {
@@ -2431,11 +2607,13 @@ async function createDocsMcpServer(options) {
2431
2607
  name: resolved.name,
2432
2608
  version: resolved.version
2433
2609
  });
2610
+ const contentChangesEnabled = resolveDocsContentChangesConfig(options.contentChanges).enabled && resolved.tools.listContentChanges !== false;
2434
2611
  const server = new McpServer({
2435
2612
  name: resolved.name,
2436
2613
  version: resolved.version
2437
2614
  });
2438
2615
  installDocsMcpSdkRegistrationPagination(server, protocolScope);
2616
+ if (contentChangesEnabled) server.server.registerCapabilities({ resources: { subscribe: true } });
2439
2617
  const registerResource = (name, uri, metadata, callback) => {
2440
2618
  return server.registerResource(name, uri, metadata, callback);
2441
2619
  };
@@ -2472,6 +2650,26 @@ async function createDocsMcpServer(options) {
2472
2650
  const defaultPages = dedupePages(await getSourcePages());
2473
2651
  const defaultTree = await getSourceNavigation();
2474
2652
  const defaultSkills = await getSourceSkills();
2653
+ const contentChangeFeed = options.contentChangeFeed ?? createDocsContentChangeFeed(options.contentChanges);
2654
+ async function resolveContentChanges(since, locale) {
2655
+ if (!contentChangesEnabled) throw new ProtocolError(ProtocolErrorCode.MethodNotFound, "Content changes are disabled.");
2656
+ if (since && !isDocsContentChangeGeneration(since)) throw new ProtocolError(ProtocolErrorCode.InvalidParams, "Content-change `since` must be a SHA-256 index generation.");
2657
+ const resolvedLocale = resolveSourceLocale(locale);
2658
+ try {
2659
+ return await contentChangeFeed.resolve({
2660
+ pages: toSearchSourcePages(dedupePages(await options.source.getPages(resolvedLocale, options.requestContext))),
2661
+ search: options.search,
2662
+ audience: "agent",
2663
+ locale: resolvedLocale,
2664
+ baseUrl: options.source.baseUrl ?? (options.requestContext?.request ? new URL(options.requestContext.request.url).origin : void 0),
2665
+ ...since ? { since } : {},
2666
+ ...options.requestContext?.request ? { request: options.requestContext.request.clone() } : {}
2667
+ });
2668
+ } catch (error) {
2669
+ if (error instanceof DocsContentChangesRequestError) throw new ProtocolError(ProtocolErrorCode.InvalidParams, error.message);
2670
+ throw error;
2671
+ }
2672
+ }
2475
2673
  registerResource("docs-navigation", "docs://navigation", {
2476
2674
  title: "Docs Navigation",
2477
2675
  description: "Structured navigation tree for the documentation site.",
@@ -2481,6 +2679,65 @@ async function createDocsMcpServer(options) {
2481
2679
  mimeType: "text/plain",
2482
2680
  text: renderNavigationTree(defaultTree)
2483
2681
  }] }));
2682
+ if (contentChangesEnabled) {
2683
+ const readChanges = async (generation) => {
2684
+ const result = await resolveContentChanges(generation && generation !== "current" ? generation : void 0);
2685
+ return { contents: [{
2686
+ uri: generation && generation !== "current" ? `docs://changes/${generation}` : DOCS_MCP_CONTENT_CHANGES_CURRENT_URI,
2687
+ mimeType: "application/json",
2688
+ text: JSON.stringify(result, null, 2)
2689
+ }] };
2690
+ };
2691
+ registerResource("docs-content-changes-current", DOCS_MCP_CONTENT_CHANGES_CURRENT_URI, {
2692
+ title: "Current docs content changes",
2693
+ description: "Stable subscription target for body-free documentation change notifications.",
2694
+ mimeType: "application/json"
2695
+ }, async () => readChanges());
2696
+ server.registerResource("docs-content-changes-by-generation", new ResourceTemplate(DOCS_MCP_CONTENT_CHANGES_URI_TEMPLATE, { list: async () => {
2697
+ return { resources: [{
2698
+ uri: `docs://changes/${(await resolveContentChanges()).indexGeneration}`,
2699
+ name: "Docs changes from current generation",
2700
+ title: "Docs changes from current generation",
2701
+ description: "Read this URI later to retrieve changes since the listed generation.",
2702
+ mimeType: "application/json"
2703
+ }] };
2704
+ } }), {
2705
+ title: "Docs content changes by generation",
2706
+ description: "Body-free documentation changes from a SHA-256 index generation to the current generation.",
2707
+ mimeType: "application/json"
2708
+ }, async (_uri, variables) => {
2709
+ const generation = variables.generation;
2710
+ if (typeof generation !== "string" || !isDocsContentChangeGeneration(generation)) throw new ProtocolError(ProtocolErrorCode.InvalidParams, "Change resource generation must be a SHA-256 index generation.");
2711
+ return readChanges(generation);
2712
+ });
2713
+ registerTool("list_content_changes", {
2714
+ title: "List docs content changes",
2715
+ description: "Synchronize body-free document metadata. Save indexGeneration and pass it as since on the next poll. A reset response means the named snapshot is no longer available.",
2716
+ inputSchema: listContentChangesInputSchema,
2717
+ outputSchema: contentChangesOutputSchema,
2718
+ annotations: { readOnlyHint: true }
2719
+ }, async ({ since, locale }) => {
2720
+ const startedAt = nowMs();
2721
+ const result = await resolveContentChanges(since, locale);
2722
+ trackMcpTool("list_content_changes", {
2723
+ locale,
2724
+ resultCount: result.counts.added + result.counts.changed + result.counts.deleted
2725
+ });
2726
+ await emitDocsAnalyticsEvent(options.analytics, {
2727
+ type: "mcp_tool",
2728
+ source: "mcp",
2729
+ locale,
2730
+ properties: {
2731
+ tool: "list_content_changes",
2732
+ mode: result.mode,
2733
+ resetRequired: result.resetRequired,
2734
+ resultCount: result.counts.added + result.counts.changed + result.counts.deleted,
2735
+ durationMs: durationMs(startedAt)
2736
+ }
2737
+ });
2738
+ return createStructuredTextResult(result);
2739
+ });
2740
+ }
2484
2741
  for (const page of defaultPages) {
2485
2742
  const resourceUri = toPageResourceUri(page.url);
2486
2743
  registerResource(`page-${slugToKey(page.slug)}`, resourceUri, {
@@ -3115,6 +3372,112 @@ async function createDocsMcpServer(options) {
3115
3372
  throw error;
3116
3373
  }
3117
3374
  });
3375
+ if (resolved.tools.searchFacets) registerTool("list_search_facets", {
3376
+ title: "List documentation search facets",
3377
+ 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.",
3378
+ inputSchema: searchFacetsInputSchema,
3379
+ outputSchema: searchFacetsOutputSchema,
3380
+ annotations: { readOnlyHint: true }
3381
+ }, async ({ locale, audience, facet, limit, cursor, framework, version, package: packageName, tags }) => {
3382
+ const startedAt = nowMs();
3383
+ const resolvedAudience = audience ?? "agent";
3384
+ const resolvedLocale = resolveSourceLocale(locale);
3385
+ const filters = {
3386
+ framework,
3387
+ version,
3388
+ package: packageName,
3389
+ tags
3390
+ };
3391
+ const trace = createDocsAgentTraceContext("mcp.tool.list_search_facets");
3392
+ const callSpanId = createDocsAgentTraceId("span");
3393
+ await emitDocsAgentTraceEvent(options.observability, {
3394
+ type: "tool.call",
3395
+ source: "mcp",
3396
+ traceId: trace.traceId,
3397
+ spanId: callSpanId,
3398
+ name: "list_search_facets",
3399
+ startedAt: trace.startedAt,
3400
+ status: "started",
3401
+ locale,
3402
+ inputPreview: {
3403
+ locale,
3404
+ audience: resolvedAudience,
3405
+ facet,
3406
+ limit,
3407
+ hasCursor: cursor !== void 0,
3408
+ filterFields: Object.entries(filters).filter(([, value]) => value !== void 0).map(([field]) => field)
3409
+ },
3410
+ metadata: { tool: "list_search_facets" }
3411
+ });
3412
+ try {
3413
+ const facets = await buildDocsSearchFacets({
3414
+ pages: toSearchSourcePages(dedupePages(await options.source.getPages(resolvedLocale, options.requestContext))),
3415
+ search: toolSearchConfig ?? true,
3416
+ audience: resolvedAudience,
3417
+ filters,
3418
+ facet,
3419
+ limit,
3420
+ cursor,
3421
+ locale: resolvedLocale,
3422
+ baseUrl: options.source.baseUrl ?? (options.requestContext?.request ? new URL(options.requestContext.request.url).origin : void 0)
3423
+ });
3424
+ const elapsed = durationMs(startedAt);
3425
+ await emitDocsAnalyticsEvent(options.analytics, {
3426
+ type: "mcp_tool",
3427
+ source: "mcp",
3428
+ locale,
3429
+ properties: {
3430
+ tool: "list_search_facets",
3431
+ audience: resolvedAudience,
3432
+ matchedPageCount: facets.matchedPageCount,
3433
+ frameworkCount: facets.facets.framework.valueCount,
3434
+ versionCount: facets.facets.version.valueCount,
3435
+ packageCount: facets.facets.package.valueCount,
3436
+ tagCount: facets.facets.tags.valueCount,
3437
+ durationMs: elapsed
3438
+ }
3439
+ });
3440
+ trackMcpTool("list_search_facets", {
3441
+ locale,
3442
+ resultCount: facets.matchedPageCount
3443
+ });
3444
+ await emitDocsAgentTraceEvent(options.observability, {
3445
+ type: "tool.result",
3446
+ source: "mcp",
3447
+ traceId: trace.traceId,
3448
+ parentSpanId: callSpanId,
3449
+ name: "list_search_facets",
3450
+ startedAt: trace.startedAt,
3451
+ endedAt: (/* @__PURE__ */ new Date()).toISOString(),
3452
+ durationMs: elapsed,
3453
+ status: "success",
3454
+ locale,
3455
+ outputPreview: {
3456
+ matchedPageCount: facets.matchedPageCount,
3457
+ facetValueCount: Object.values(facets.facets).reduce((total, facet) => total + facet.valueCount, 0)
3458
+ },
3459
+ metadata: { tool: "list_search_facets" }
3460
+ });
3461
+ return createStructuredTextResult(facets);
3462
+ } catch (error) {
3463
+ const elapsed = durationMs(startedAt);
3464
+ await emitDocsAgentTraceEvent(options.observability, {
3465
+ type: "tool.error",
3466
+ source: "mcp",
3467
+ traceId: trace.traceId,
3468
+ parentSpanId: callSpanId,
3469
+ name: "list_search_facets",
3470
+ startedAt: trace.startedAt,
3471
+ endedAt: (/* @__PURE__ */ new Date()).toISOString(),
3472
+ durationMs: elapsed,
3473
+ status: "error",
3474
+ locale,
3475
+ outputPreview: { message: error instanceof Error ? error.message : "Unknown error" },
3476
+ metadata: { tool: "list_search_facets" }
3477
+ });
3478
+ throw error;
3479
+ }
3480
+ });
3118
3481
  if (resolved.tools.searchDocs) registerTool("search_docs", {
3119
3482
  title: "Search documentation",
3120
3483
  description: "Search the docs by keyword with optional framework, version, package, and tag filters. Returns structured ambiguity and metadata warnings.",
@@ -3861,20 +4224,48 @@ function createDocsMcpHttpHandler(options) {
3861
4224
  data: { reason: "mcp_disabled" }
3862
4225
  }),
3863
4226
  DELETE: async () => createJsonErrorResponse(404, disabledMessage),
3864
- OPTIONS: async () => createJsonErrorResponse(404, disabledMessage)
4227
+ OPTIONS: async () => createJsonErrorResponse(404, disabledMessage),
4228
+ close: async () => {}
3865
4229
  };
3866
- async function createStatelessTransport(requestContext) {
3867
- const server = await createDocsMcpServer({
4230
+ const contentChangesEnabled = resolveDocsContentChangesConfig(options.contentChanges).enabled && resolved.tools.listContentChanges !== false;
4231
+ const contentChangeFeed = options.contentChangeFeed ?? createDocsContentChangeFeed(options.contentChanges);
4232
+ const eventBus = new InMemoryServerEventBus();
4233
+ const mcpHandler = createMcpHandler(async (context) => {
4234
+ const internalAuth = context.authInfo;
4235
+ return createDocsMcpServer({
3868
4236
  ...options,
3869
- requestContext
4237
+ contentChangeFeed,
4238
+ requestContext: {
4239
+ transport: "http",
4240
+ ...context.requestInfo ? { request: context.requestInfo.clone() } : {},
4241
+ ...internalAuth?.principal ? { auth: internalAuth.principal } : {}
4242
+ }
3870
4243
  });
3871
- const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
3872
- await server.connect(transport);
3873
- return {
3874
- server,
3875
- transport
3876
- };
4244
+ }, {
4245
+ legacy: "stateless",
4246
+ bus: eventBus
4247
+ });
4248
+ const configuredPollInterval = options.contentChangePollIntervalMs;
4249
+ const contentChangePollIntervalMs = typeof configuredPollInterval === "number" && Number.isFinite(configuredPollInterval) && configuredPollInterval >= 10 ? Math.floor(configuredPollInterval) : DEFAULT_DOCS_MCP_CONTENT_CHANGE_POLL_INTERVAL_MS;
4250
+ async function readMonitoredGeneration(context) {
4251
+ const locale = options.source.resolveLocale?.(void 0, context);
4252
+ const pages = dedupePages(await options.source.getPages(locale, context));
4253
+ return (await contentChangeFeed.resolve({
4254
+ pages: toSearchSourcePages(pages),
4255
+ search: options.search,
4256
+ audience: "agent",
4257
+ locale,
4258
+ baseUrl: options.source.baseUrl ?? (context.request ? new URL(context.request.url).origin : void 0),
4259
+ ...context.request ? { request: context.request.clone() } : {}
4260
+ })).indexGeneration;
3877
4261
  }
4262
+ const contentChangeMonitor = createDocsMcpContentChangeMonitor({
4263
+ pollIntervalMs: contentChangePollIntervalMs,
4264
+ readGeneration: readMonitoredGeneration,
4265
+ notify: () => mcpHandler.notify.resourceUpdated(DOCS_MCP_CONTENT_CHANGES_CURRENT_URI),
4266
+ isActive: () => eventBus.listenerCount > 0,
4267
+ unrefTimer: true
4268
+ });
3878
4269
  async function handle(request) {
3879
4270
  const originalUrl = new URL(request.url);
3880
4271
  const method = request.method.toUpperCase();
@@ -3957,26 +4348,77 @@ function createDocsMcpHttpHandler(options) {
3957
4348
  code: -32700,
3958
4349
  message: "Parse error: Invalid JSON"
3959
4350
  }));
3960
- return withCors(await (await createStatelessTransport({
4351
+ const requestContext = {
3961
4352
  transport: "http",
3962
4353
  request: request.clone(),
3963
4354
  auth
3964
- })).transport.handleRequest(request, parsedBody === void 0 ? void 0 : { parsedBody }));
4355
+ };
4356
+ const isContentChangeSubscription = contentChangesEnabled && method === "POST" && typeof parsedBody === "object" && parsedBody !== null && !Array.isArray(parsedBody) && parsedBody.method === "subscriptions/listen";
4357
+ const authInfo = auth ? {
4358
+ token: "",
4359
+ clientId: auth.id,
4360
+ scopes: auth.scopes ?? [],
4361
+ principal: auth
4362
+ } : void 0;
4363
+ const response = await mcpHandler.fetch(request, {
4364
+ ...parsedBody === void 0 ? {} : { parsedBody },
4365
+ ...authInfo ? { authInfo } : {}
4366
+ });
4367
+ if (isContentChangeSubscription) await contentChangeMonitor.start(requestContext);
4368
+ return withCors(response);
3965
4369
  }
3966
4370
  return {
3967
4371
  GET: async ({ request }) => handle(request),
3968
4372
  POST: async ({ request }) => handle(request),
3969
4373
  DELETE: async ({ request }) => handle(request),
3970
- OPTIONS: async ({ request }) => handle(request)
4374
+ OPTIONS: async ({ request }) => handle(request),
4375
+ close: async () => {
4376
+ contentChangeMonitor.close();
4377
+ await mcpHandler.close();
4378
+ }
3971
4379
  };
3972
4380
  }
3973
4381
  async function runDocsMcpStdio(options) {
3974
- const server = await createDocsMcpServer({
3975
- ...options,
3976
- requestContext: { transport: "stdio" }
4382
+ const resolved = resolveDocsMcpConfig(options.mcp, {
4383
+ defaultName: options.defaultName ?? options.source.siteTitle ?? DEFAULT_MCP_NAME,
4384
+ defaultVersion: options.defaultVersion
4385
+ });
4386
+ const contentChangesEnabled = resolveDocsContentChangesConfig(options.contentChanges).enabled && resolved.tools.listContentChanges !== false;
4387
+ const contentChangeFeed = options.contentChangeFeed ?? createDocsContentChangeFeed(options.contentChanges);
4388
+ const configuredPollInterval = options.contentChangePollIntervalMs;
4389
+ const pollIntervalMs = typeof configuredPollInterval === "number" && Number.isFinite(configuredPollInterval) && configuredPollInterval >= 10 ? Math.floor(configuredPollInterval) : DEFAULT_DOCS_MCP_CONTENT_CHANGE_POLL_INTERVAL_MS;
4390
+ serveStdio(async () => {
4391
+ const requestContext = { transport: "stdio" };
4392
+ const server = await createDocsMcpServer({
4393
+ ...options,
4394
+ contentChangeFeed,
4395
+ requestContext
4396
+ });
4397
+ if (!contentChangesEnabled) return server;
4398
+ const readGeneration = async () => {
4399
+ const locale = options.source.resolveLocale?.(void 0, requestContext);
4400
+ const pages = dedupePages(await options.source.getPages(locale, requestContext));
4401
+ return (await contentChangeFeed.resolve({
4402
+ pages: toSearchSourcePages(pages),
4403
+ search: options.search,
4404
+ audience: "agent",
4405
+ locale,
4406
+ baseUrl: options.source.baseUrl
4407
+ })).indexGeneration;
4408
+ };
4409
+ const monitor = createDocsMcpContentChangeMonitor({
4410
+ pollIntervalMs,
4411
+ readGeneration: () => readGeneration(),
4412
+ notify: () => server.server.sendResourceUpdated({ uri: DOCS_MCP_CONTENT_CHANGES_CURRENT_URI })
4413
+ });
4414
+ await monitor.start(requestContext);
4415
+ const closeServer = server.close.bind(server);
4416
+ server.close = async () => {
4417
+ monitor.close();
4418
+ await closeServer();
4419
+ };
4420
+ return server;
3977
4421
  });
3978
- const transport = new StdioServerTransport();
3979
- await server.connect(transport);
3980
4422
  }
3981
4423
  async function isDocsMcpOriginAllowed(request, allowedOrigins) {
3982
4424
  const origin = request.headers.get("origin");
@@ -5417,4 +5859,4 @@ function toPageResourceUri(url) {
5417
5859
  }
5418
5860
 
5419
5861
  //#endregion
5420
- export { DEFAULT_DOCS_MCP_CORS_ALLOWED_HEADERS, DEFAULT_DOCS_MCP_CORS_EXPOSED_HEADERS, DEFAULT_DOCS_MCP_CORS_MAX_AGE_SECONDS, DEFAULT_DOCS_MCP_MAX_BODY_BYTES, DOCS_CONFIG_SCHEMA_OPTIONS, buildDocsMcpContext, createDocsMcpHttpHandler, createDocsMcpServer, createFilesystemDocsMcpSource, getDocsConfigSchema, normalizeDocsMcpRoute, resolveDocsMcpConfig, runDocsMcpStdio };
5862
+ export { DEFAULT_DOCS_MCP_CONTENT_CHANGE_POLL_INTERVAL_MS, DEFAULT_DOCS_MCP_CORS_ALLOWED_HEADERS, DEFAULT_DOCS_MCP_CORS_EXPOSED_HEADERS, DEFAULT_DOCS_MCP_CORS_MAX_AGE_SECONDS, DEFAULT_DOCS_MCP_MAX_BODY_BYTES, DOCS_CONFIG_SCHEMA_OPTIONS, DOCS_MCP_CONTENT_CHANGES_CURRENT_URI, DOCS_MCP_CONTENT_CHANGES_URI_TEMPLATE, buildDocsMcpContext, createDocsMcpHttpHandler, createDocsMcpServer, createFilesystemDocsMcpSource, getDocsConfigSchema, normalizeDocsMcpRoute, resolveDocsMcpConfig, runDocsMcpStdio };