{"version":3,"file":"server-BV5OpOeA.mjs","names":[],"sources":["../src/mcp/server.ts"],"sourcesContent":["import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { asTextResource, getPackageVersion, resolvePackageDocPath } from \"@pagesmith/core/mcp\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { basename, isAbsolute, relative, resolve } from \"path\";\nimport { z } from \"zod\";\nimport { loadDocsPages, loadSectionMetas } from \"../content.js\";\nimport { resolveDocsConfigAsync, validateConfig, type ResolvedDocsConfig } from \"../config.js\";\n\nexport type DocsMcpServerOptions = {\n  rootDir?: string;\n  configPath?: string;\n};\n\nfunction ensurePathWithinRoot(baseRoot: string, candidatePath: string): string {\n  const resolvedRoot = resolve(baseRoot);\n  const resolvedPath = isAbsolute(candidatePath)\n    ? resolve(candidatePath)\n    : resolve(resolvedRoot, candidatePath);\n  const rel = relative(resolvedRoot, resolvedPath);\n  if (rel && rel.startsWith(\"..\")) {\n    throw new Error(\n      `Config path must stay within the docs root directory: ${resolvedRoot}. Received: ${candidatePath}`,\n    );\n  }\n  return resolvedPath;\n}\n\nfunction withConfigPath(baseRoot: string, configPath?: string): string {\n  const candidate = configPath ?? \"pagesmith.config.json5\";\n  return ensurePathWithinRoot(baseRoot, candidate);\n}\n\nfunction resolveConfig(baseRoot: string, configPath?: string): Promise<ResolvedDocsConfig> {\n  const absolutePath = withConfigPath(baseRoot, configPath);\n  return resolveDocsConfigAsync(absolutePath);\n}\n\nexport function createDocsMcpServer(options: DocsMcpServerOptions = {}): McpServer {\n  const baseRoot = resolve(options.rootDir ?? process.cwd());\n  const defaultConfigPath = withConfigPath(baseRoot, options.configPath);\n\n  const server = new McpServer(\n    {\n      name: \"@pagesmith/docs-mcp\",\n      version: getPackageVersion(import.meta.dirname),\n    },\n    {\n      instructions: [\n        \"Use docs_* tools to inspect @pagesmith/docs projects.\",\n        `Default config path: ${defaultConfigPath}`,\n      ].join(\"\\n\"),\n    },\n  );\n\n  server.registerTool(\n    \"docs_validate_config\",\n    {\n      description: \"Validate pagesmith.config.json5 and return warnings/errors.\",\n      inputSchema: {\n        configPath: z.string().optional(),\n      },\n    },\n    async ({ configPath }: { configPath?: string }) => {\n      const config = await resolveConfig(baseRoot, configPath);\n      const issues = validateConfig(config);\n      const hasErrors = issues.some((issue) => issue.severity === \"error\");\n      return {\n        content: [\n          {\n            type: \"text\",\n            text: JSON.stringify(\n              {\n                configPath: withConfigPath(baseRoot, configPath),\n                hasErrors,\n                issues,\n              },\n              null,\n              2,\n            ),\n          },\n        ],\n      };\n    },\n  );\n\n  server.registerTool(\n    \"docs_resolve_config\",\n    {\n      description: \"Resolve effective docs config including defaults and inferred values.\",\n      inputSchema: {\n        configPath: z.string().optional(),\n      },\n    },\n    async ({ configPath }: { configPath?: string }) => {\n      const config = await resolveConfig(baseRoot, configPath);\n      return {\n        content: [\n          {\n            type: \"text\",\n            text: JSON.stringify(\n              {\n                rootDir: config.rootDir,\n                contentDir: config.contentDir,\n                outDir: config.outDir,\n                basePath: config.basePath,\n                title: config.title,\n                name: config.name,\n                origin: config.origin,\n                search: config.search,\n                sidebar: config.sidebar,\n                theme: config.theme,\n              },\n              null,\n              2,\n            ),\n          },\n        ],\n      };\n    },\n  );\n\n  server.registerTool(\n    \"docs_list_pages\",\n    {\n      description: \"List resolved docs pages with route paths and source files.\",\n      inputSchema: {\n        configPath: z.string().optional(),\n        section: z.string().optional(),\n      },\n    },\n    async ({ configPath, section }: { configPath?: string; section?: string }) => {\n      const config = await resolveConfig(baseRoot, configPath);\n      const sectionMetas = loadSectionMetas(config.contentDir);\n      const pages = await loadDocsPages(config, sectionMetas);\n      const filtered = section ? pages.filter((page) => page.section === section) : pages;\n\n      return {\n        content: [\n          {\n            type: \"text\",\n            text: JSON.stringify(\n              {\n                count: filtered.length,\n                pages: filtered.map((page) => ({\n                  title: page.title,\n                  routePath: page.routePath,\n                  contentSlug: page.contentSlug,\n                  section: page.section,\n                  sourcePath: page.sourcePath,\n                  isHome: page.isHome,\n                  lastUpdated: page.lastUpdated,\n                })),\n              },\n              null,\n              2,\n            ),\n          },\n        ],\n      };\n    },\n  );\n\n  server.registerTool(\n    \"docs_get_page\",\n    {\n      description: \"Get one docs page by slug or route and return metadata plus markdown source.\",\n      inputSchema: {\n        slug: z.string(),\n        configPath: z.string().optional(),\n      },\n    },\n    async ({ slug, configPath }: { slug: string; configPath?: string }) => {\n      const normalized =\n        slug === \"/\" ? \"/\" : `/${slug}`.replace(/\\/+/g, \"/\").replace(/\\/$/, \"\").replace(/^\\//, \"\");\n      const config = await resolveConfig(baseRoot, configPath);\n      const sectionMetas = loadSectionMetas(config.contentDir);\n      const pages = await loadDocsPages(config, sectionMetas);\n\n      const page = pages.find((entry) => {\n        if (slug === \"/\") return entry.contentSlug === \"/\";\n        return (\n          entry.contentSlug === normalized ||\n          entry.routePath === `/${normalized}` ||\n          entry.routePath === `/${normalized}/`\n        );\n      });\n\n      if (!page) {\n        throw new Error(`Page not found for slug \"${slug}\"`);\n      }\n\n      const sourceMarkdown = existsSync(page.sourcePath)\n        ? readFileSync(page.sourcePath, \"utf-8\")\n        : \"\";\n\n      return {\n        content: [\n          {\n            type: \"text\",\n            text: JSON.stringify(\n              {\n                title: page.title,\n                routePath: page.routePath,\n                contentSlug: page.contentSlug,\n                section: page.section,\n                sourcePath: page.sourcePath,\n                headings: page.headings,\n                sourceMarkdown,\n              },\n              null,\n              2,\n            ),\n          },\n        ],\n      };\n    },\n  );\n\n  server.registerTool(\n    \"docs_search_pages\",\n    {\n      description:\n        \"Search docs pages by matching a query string against titles, descriptions, and raw markdown content. Returns up to 20 matching pages with a content snippet. Case-insensitive.\",\n      inputSchema: {\n        query: z.string().describe(\"Search query string\"),\n        section: z.string().optional().describe(\"Filter results to a specific section\"),\n        configPath: z.string().optional().describe(\"Path to pagesmith.config.json5\"),\n      },\n    },\n    async ({\n      query,\n      section,\n      configPath,\n    }: {\n      query: string;\n      section?: string;\n      configPath?: string;\n    }) => {\n      if (!query.trim()) {\n        return {\n          content: [\n            {\n              type: \"text\",\n              text: JSON.stringify({ query, matches: [], count: 0 }, null, 2),\n            },\n          ],\n        };\n      }\n\n      const config = await resolveConfig(baseRoot, configPath);\n      const sectionMetas = loadSectionMetas(config.contentDir);\n      const pages = await loadDocsPages(config, sectionMetas);\n      const filtered = section ? pages.filter((page) => page.section === section) : pages;\n\n      const queryLower = query.toLowerCase();\n      const maxResults = 20;\n      const snippetRadius = 120;\n\n      type SearchMatch = {\n        slug: string;\n        title: string;\n        section: string | undefined;\n        snippet: string;\n      };\n\n      const matches: SearchMatch[] = [];\n\n      for (const page of filtered) {\n        if (matches.length >= maxResults) break;\n\n        const titleMatch = page.title.toLowerCase().includes(queryLower);\n        const descriptionText =\n          typeof page.frontmatter.description === \"string\" ? page.frontmatter.description : \"\";\n        const descriptionMatch = descriptionText.toLowerCase().includes(queryLower);\n\n        // Read raw markdown for content search\n        const rawMarkdown = existsSync(page.sourcePath)\n          ? readFileSync(page.sourcePath, \"utf-8\")\n          : \"\";\n        const contentMatch = rawMarkdown.toLowerCase().includes(queryLower);\n\n        if (!titleMatch && !descriptionMatch && !contentMatch) continue;\n\n        // Build a snippet from the first match location\n        let snippet: string;\n        if (titleMatch) {\n          snippet = `[title] ${page.title}`;\n        } else if (descriptionMatch) {\n          snippet = `[description] ${descriptionText}`;\n        } else {\n          // Extract a snippet around the first content match\n          const matchIndex = rawMarkdown.toLowerCase().indexOf(queryLower);\n          const start = Math.max(0, matchIndex - snippetRadius);\n          const end = Math.min(rawMarkdown.length, matchIndex + query.length + snippetRadius);\n          const raw = rawMarkdown.slice(start, end).replace(/\\n+/g, \" \").trim();\n          snippet = (start > 0 ? \"...\" : \"\") + raw + (end < rawMarkdown.length ? \"...\" : \"\");\n        }\n\n        matches.push({\n          slug: page.contentSlug,\n          title: page.title,\n          section: page.section,\n          snippet,\n        });\n      }\n\n      return {\n        content: [\n          {\n            type: \"text\",\n            text: JSON.stringify(\n              {\n                query,\n                section: section ?? null,\n                count: matches.length,\n                matches,\n              },\n              null,\n              2,\n            ),\n          },\n        ],\n      };\n    },\n  );\n\n  server.registerResource(\n    \"docs-agent-usage\",\n    \"pagesmith://docs/agents/usage\",\n    {\n      title: \"@pagesmith/docs agent usage\",\n      description: \"Version-matched AI usage guide for @pagesmith/docs.\",\n      mimeType: \"text/markdown\",\n    },\n    async () =>\n      asTextResource(\n        \"pagesmith://docs/agents/usage\",\n        resolvePackageDocPath(\n          import.meta.dirname,\n          \"skills/pagesmith-docs-setup/references/usage.md\",\n        ),\n      ),\n  );\n\n  server.registerResource(\n    \"docs-llms-full\",\n    \"pagesmith://docs/llms-full\",\n    {\n      title: \"@pagesmith/docs llms-full\",\n      description: \"Version-matched full AI reference for @pagesmith/docs.\",\n      mimeType: \"text/markdown\",\n    },\n    async () =>\n      asTextResource(\n        \"pagesmith://docs/llms-full\",\n        resolvePackageDocPath(import.meta.dirname, \"llms-full.txt\"),\n      ),\n  );\n\n  server.registerResource(\n    \"docs-reference\",\n    \"pagesmith://docs/reference\",\n    {\n      title: \"@pagesmith/docs reference\",\n      description: \"Full package reference for docs configuration, CLI, and layouts.\",\n      mimeType: \"text/markdown\",\n    },\n    async () =>\n      asTextResource(\n        \"pagesmith://docs/reference\",\n        resolvePackageDocPath(import.meta.dirname, \"REFERENCE.md\"),\n      ),\n  );\n\n  server.registerResource(\n    \"core-reference\",\n    \"pagesmith://core/reference\",\n    {\n      title: \"@pagesmith/core reference\",\n      description: \"Core package reference for content layer and markdown pipeline.\",\n      mimeType: \"text/markdown\",\n    },\n    async () => {\n      const { fileURLToPath } = await import(\"url\");\n      const { dirname, join } = await import(\"path\");\n      const corePkgPath = fileURLToPath(import.meta.resolve(\"@pagesmith/core/package.json\"));\n      const coreRefPath = join(dirname(corePkgPath), \"REFERENCE.md\");\n      return asTextResource(\"pagesmith://core/reference\", coreRefPath);\n    },\n  );\n\n  return server;\n}\n\nexport async function startDocsMcpServer(options: DocsMcpServerOptions = {}): Promise<void> {\n  const server = createDocsMcpServer(options);\n  const transport = new StdioServerTransport();\n  await server.connect(transport);\n  // Keep logs on stderr; stdout is reserved for MCP protocol messages.\n  console.error(\n    `[pagesmith:mcp] @pagesmith/docs MCP server started (root=${basename(resolve(options.rootDir ?? process.cwd()))})`,\n  );\n}\n"],"mappings":";;;;;;;;;AAcA,SAAS,qBAAqB,UAAkB,eAA+B;CAC7E,MAAM,eAAe,QAAQ,QAAQ;CACrC,MAAM,eAAe,WAAW,aAAa,IACzC,QAAQ,aAAa,IACrB,QAAQ,cAAc,aAAa;CACvC,MAAM,MAAM,SAAS,cAAc,YAAY;CAC/C,IAAI,OAAO,IAAI,WAAW,IAAI,GAC5B,MAAM,IAAI,MACR,yDAAyD,aAAa,cAAc,eACtF;CAEF,OAAO;AACT;AAEA,SAAS,eAAe,UAAkB,YAA6B;CAErE,OAAO,qBAAqB,UADV,cAAc,wBACe;AACjD;AAEA,SAAS,cAAc,UAAkB,YAAkD;CAEzF,OAAO,uBADc,eAAe,UAAU,UACL,CAAC;AAC5C;AAEA,SAAgB,oBAAoB,UAAgC,CAAC,GAAc;CACjF,MAAM,WAAW,QAAQ,QAAQ,WAAW,QAAQ,IAAI,CAAC;CACzD,MAAM,oBAAoB,eAAe,UAAU,QAAQ,UAAU;CAErE,MAAM,SAAS,IAAI,UACjB;EACE,MAAM;EACN,SAAS,kBAAkB,OAAO,KAAK,OAAO;CAChD,GACA,EACE,cAAc,CACZ,yDACA,wBAAwB,mBAC1B,EAAE,KAAK,IAAI,EACb,CACF;CAEA,OAAO,aACL,wBACA;EACE,aAAa;EACb,aAAa,EACX,YAAY,EAAE,OAAO,EAAE,SAAS,EAClC;CACF,GACA,OAAO,EAAE,iBAA0C;EAEjD,MAAM,SAAS,eAAe,MADT,cAAc,UAAU,UAAU,CACnB;EACpC,MAAM,YAAY,OAAO,MAAM,UAAU,MAAM,aAAa,OAAO;EACnE,OAAO,EACL,SAAS,CACP;GACE,MAAM;GACN,MAAM,KAAK,UACT;IACE,YAAY,eAAe,UAAU,UAAU;IAC/C;IACA;GACF,GACA,MACA,CACF;EACF,CACF,EACF;CACF,CACF;CAEA,OAAO,aACL,uBACA;EACE,aAAa;EACb,aAAa,EACX,YAAY,EAAE,OAAO,EAAE,SAAS,EAClC;CACF,GACA,OAAO,EAAE,iBAA0C;EACjD,MAAM,SAAS,MAAM,cAAc,UAAU,UAAU;EACvD,OAAO,EACL,SAAS,CACP;GACE,MAAM;GACN,MAAM,KAAK,UACT;IACE,SAAS,OAAO;IAChB,YAAY,OAAO;IACnB,QAAQ,OAAO;IACf,UAAU,OAAO;IACjB,OAAO,OAAO;IACd,MAAM,OAAO;IACb,QAAQ,OAAO;IACf,QAAQ,OAAO;IACf,SAAS,OAAO;IAChB,OAAO,OAAO;GAChB,GACA,MACA,CACF;EACF,CACF,EACF;CACF,CACF;CAEA,OAAO,aACL,mBACA;EACE,aAAa;EACb,aAAa;GACX,YAAY,EAAE,OAAO,EAAE,SAAS;GAChC,SAAS,EAAE,OAAO,EAAE,SAAS;EAC/B;CACF,GACA,OAAO,EAAE,YAAY,cAAyD;EAC5E,MAAM,SAAS,MAAM,cAAc,UAAU,UAAU;EAEvD,MAAM,QAAQ,MAAM,cAAc,QADb,iBAAiB,OAAO,UACQ,CAAC;EACtD,MAAM,WAAW,UAAU,MAAM,QAAQ,SAAS,KAAK,YAAY,OAAO,IAAI;EAE9E,OAAO,EACL,SAAS,CACP;GACE,MAAM;GACN,MAAM,KAAK,UACT;IACE,OAAO,SAAS;IAChB,OAAO,SAAS,KAAK,UAAU;KAC7B,OAAO,KAAK;KACZ,WAAW,KAAK;KAChB,aAAa,KAAK;KAClB,SAAS,KAAK;KACd,YAAY,KAAK;KACjB,QAAQ,KAAK;KACb,aAAa,KAAK;IACpB,EAAE;GACJ,GACA,MACA,CACF;EACF,CACF,EACF;CACF,CACF;CAEA,OAAO,aACL,iBACA;EACE,aAAa;EACb,aAAa;GACX,MAAM,EAAE,OAAO;GACf,YAAY,EAAE,OAAO,EAAE,SAAS;EAClC;CACF,GACA,OAAO,EAAE,MAAM,iBAAwD;EACrE,MAAM,aACJ,SAAS,MAAM,MAAM,IAAI,OAAO,QAAQ,QAAQ,GAAG,EAAE,QAAQ,OAAO,EAAE,EAAE,QAAQ,OAAO,EAAE;EAC3F,MAAM,SAAS,MAAM,cAAc,UAAU,UAAU;EAIvD,MAAM,QAAO,MAFO,cAAc,QADb,iBAAiB,OAAO,UACQ,CAAC,GAEnC,MAAM,UAAU;GACjC,IAAI,SAAS,KAAK,OAAO,MAAM,gBAAgB;GAC/C,OACE,MAAM,gBAAgB,cACtB,MAAM,cAAc,IAAI,gBACxB,MAAM,cAAc,IAAI,WAAW;EAEvC,CAAC;EAED,IAAI,CAAC,MACH,MAAM,IAAI,MAAM,4BAA4B,KAAK,EAAE;EAGrD,MAAM,iBAAiB,WAAW,KAAK,UAAU,IAC7C,aAAa,KAAK,YAAY,OAAO,IACrC;EAEJ,OAAO,EACL,SAAS,CACP;GACE,MAAM;GACN,MAAM,KAAK,UACT;IACE,OAAO,KAAK;IACZ,WAAW,KAAK;IAChB,aAAa,KAAK;IAClB,SAAS,KAAK;IACd,YAAY,KAAK;IACjB,UAAU,KAAK;IACf;GACF,GACA,MACA,CACF;EACF,CACF,EACF;CACF,CACF;CAEA,OAAO,aACL,qBACA;EACE,aACE;EACF,aAAa;GACX,OAAO,EAAE,OAAO,EAAE,SAAS,qBAAqB;GAChD,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,sCAAsC;GAC9E,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,gCAAgC;EAC7E;CACF,GACA,OAAO,EACL,OACA,SACA,iBAKI;EACJ,IAAI,CAAC,MAAM,KAAK,GACd,OAAO,EACL,SAAS,CACP;GACE,MAAM;GACN,MAAM,KAAK,UAAU;IAAE;IAAO,SAAS,CAAC;IAAG,OAAO;GAAE,GAAG,MAAM,CAAC;EAChE,CACF,EACF;EAGF,MAAM,SAAS,MAAM,cAAc,UAAU,UAAU;EAEvD,MAAM,QAAQ,MAAM,cAAc,QADb,iBAAiB,OAAO,UACQ,CAAC;EACtD,MAAM,WAAW,UAAU,MAAM,QAAQ,SAAS,KAAK,YAAY,OAAO,IAAI;EAE9E,MAAM,aAAa,MAAM,YAAY;EACrC,MAAM,aAAa;EACnB,MAAM,gBAAgB;EAStB,MAAM,UAAyB,CAAC;EAEhC,KAAK,MAAM,QAAQ,UAAU;GAC3B,IAAI,QAAQ,UAAU,YAAY;GAElC,MAAM,aAAa,KAAK,MAAM,YAAY,EAAE,SAAS,UAAU;GAC/D,MAAM,kBACJ,OAAO,KAAK,YAAY,gBAAgB,WAAW,KAAK,YAAY,cAAc;GACpF,MAAM,mBAAmB,gBAAgB,YAAY,EAAE,SAAS,UAAU;GAG1E,MAAM,cAAc,WAAW,KAAK,UAAU,IAC1C,aAAa,KAAK,YAAY,OAAO,IACrC;GACJ,MAAM,eAAe,YAAY,YAAY,EAAE,SAAS,UAAU;GAElE,IAAI,CAAC,cAAc,CAAC,oBAAoB,CAAC,cAAc;GAGvD,IAAI;GACJ,IAAI,YACF,UAAU,WAAW,KAAK;QACrB,IAAI,kBACT,UAAU,iBAAiB;QACtB;IAEL,MAAM,aAAa,YAAY,YAAY,EAAE,QAAQ,UAAU;IAC/D,MAAM,QAAQ,KAAK,IAAI,GAAG,aAAa,aAAa;IACpD,MAAM,MAAM,KAAK,IAAI,YAAY,QAAQ,aAAa,MAAM,SAAS,aAAa;IAClF,MAAM,MAAM,YAAY,MAAM,OAAO,GAAG,EAAE,QAAQ,QAAQ,GAAG,EAAE,KAAK;IACpE,WAAW,QAAQ,IAAI,QAAQ,MAAM,OAAO,MAAM,YAAY,SAAS,QAAQ;GACjF;GAEA,QAAQ,KAAK;IACX,MAAM,KAAK;IACX,OAAO,KAAK;IACZ,SAAS,KAAK;IACd;GACF,CAAC;EACH;EAEA,OAAO,EACL,SAAS,CACP;GACE,MAAM;GACN,MAAM,KAAK,UACT;IACE;IACA,SAAS,WAAW;IACpB,OAAO,QAAQ;IACf;GACF,GACA,MACA,CACF;EACF,CACF,EACF;CACF,CACF;CAEA,OAAO,iBACL,oBACA,iCACA;EACE,OAAO;EACP,aAAa;EACb,UAAU;CACZ,GACA,YACE,eACE,iCACA,sBACE,OAAO,KAAK,SACZ,iDACF,CACF,CACJ;CAEA,OAAO,iBACL,kBACA,8BACA;EACE,OAAO;EACP,aAAa;EACb,UAAU;CACZ,GACA,YACE,eACE,8BACA,sBAAsB,OAAO,KAAK,SAAS,eAAe,CAC5D,CACJ;CAEA,OAAO,iBACL,kBACA,8BACA;EACE,OAAO;EACP,aAAa;EACb,UAAU;CACZ,GACA,YACE,eACE,8BACA,sBAAsB,OAAO,KAAK,SAAS,cAAc,CAC3D,CACJ;CAEA,OAAO,iBACL,kBACA,8BACA;EACE,OAAO;EACP,aAAa;EACb,UAAU;CACZ,GACA,YAAY;EACV,MAAM,EAAE,kBAAkB,MAAM,OAAO;EACvC,MAAM,EAAE,SAAS,SAAS,MAAM,OAAO;EAGvC,OAAO,eAAe,8BADF,KAAK,QADL,cAAc,OAAO,KAAK,QAAQ,8BAA8B,CACzC,CAAC,GAAG,cACe,CAAC;CACjE,CACF;CAEA,OAAO;AACT;AAEA,eAAsB,mBAAmB,UAAgC,CAAC,GAAkB;CAC1F,MAAM,SAAS,oBAAoB,OAAO;CAC1C,MAAM,YAAY,IAAI,qBAAqB;CAC3C,MAAM,OAAO,QAAQ,SAAS;CAE9B,QAAQ,MACN,4DAA4D,SAAS,QAAQ,QAAQ,WAAW,QAAQ,IAAI,CAAC,CAAC,EAAE,EAClH;AACF"}