diff --git a/.changeset/public-carrots-search.md b/.changeset/public-carrots-search.md
new file mode 100644
index 0000000000..853d812bb3
--- /dev/null
+++ b/.changeset/public-carrots-search.md
@@ -0,0 +1,3 @@
+---
+
+---
diff --git a/packages/mcp-servers/docs-mcp-server/README.md b/packages/mcp-servers/docs-mcp-server/README.md
new file mode 100644
index 0000000000..fa6fe6c309
--- /dev/null
+++ b/packages/mcp-servers/docs-mcp-server/README.md
@@ -0,0 +1,109 @@
+# @lynx-js/docs-mcp-server
+
+> A MCP Server providing Lynx documentation resources for LLMs, with carefully designed prompting.
+
+`@lynx-js/docs-mcp-server` lets your coding agent (such as Gemini, Claude, Cursor or Copilot)
+access Lynx documentation to assist you in development tasks. Therefore,
+we have specifically optimized [llms.txt](https://lynxjs.org/next/llms.txt),
+a condensed version of the documentation site optimized for reading large models.
+
+## Requirements
+
+- [Node.js](https://nodejs.org/) v18.17 or a newer [latest maintenance LTS](https://github.com/nodejs/Release#release-schedule) version.
+
+## Getting started
+
+Add the following config to your MCP client:
+
+```json
+{
+ "mcpServers": {
+ "lynx-docs": {
+ "command": "npx",
+ "args": [
+ "-y",
+ "@lynx-js/docs-mcp-server@latest"
+ ]
+ }
+ }
+}
+```
+
+`@lynx-js/docs-mcp-server` works best with MCP clients that supports [Server Instructions](https://modelcontextprotocol.io/specification/draft/schema#initializeresult), such as Claude Code.
+If you find your MCP client don't know about the MCP server,
+you can manually provide the following instructions
+(e.g. in your `AGENTS.md`, `CLAUDE.md`, or just send it along with your question):
+
+```md
+For any questions or requirements regarding Lynx:
+
+1. Use the "List Resources Tool" to list all Resources provided in MCP "lynx-docs".
+2. First read MCP Resources "lynx-docs://llms.txt" (**REQUIRED**), this document is an ENTRYPOINT of all Lynx Docs.
+3. After reading "lynx-docs://llms.txt", use the "Read MCP Resources Tool" to retrieve docs you need based on the user's questions or requirements, please read them proactively.
+4. If available, prioritize obtaining Lynx-related information through MCP Resources tools over external web searches.
+```
+
+
+ Claude Code
+ Use the Claude Code CLI to add the Lynx Docs MCP server (guide):
+
+```bash
+claude mcp add lynx-docs npx @lynx-js/docs-mcp-server@latest
+```
+
+
+
+
+ Codex
+ Follow the configure MCP guide
+ using the standard config from above. You can also install the Lynx Docs MCP server using the Codex CLI:
+
+```bash
+codex mcp add lynx-docs -- npx @lynx-js/docs-mcp-server@latest
+```
+
+
+
+
+ Copilot / VS Code
+ Follow the MCP install guide,
+ with the standard config from above. You can also install the Lynx Docs MCP server using the VS Code CLI:
+
+```bash
+code --add-mcp '{"name":"lynx-docs","command":"npx","args":["@lynx-js/docs-mcp-server@latest"]}'
+```
+
+
+
+
+ Cursor
+
+**Install manually:**
+
+Go to `Cursor Settings` -> `MCP` -> `New MCP Server`. Use the config provided above.
+
+
+
+
+ Gemini CLI
+Install the Lynx Docs MCP server using the Gemini CLI.
+
+**Project wide:**
+
+```bash
+gemini mcp add lynx-docs npx @lynx-js/docs-mcp-server@latest
+```
+
+**Globally:**
+
+```bash
+gemini mcp add -s user lynx-docs npx @lynx-js/docs-mcp-server@latest
+```
+
+Alternatively, follow the MCP guide and use the standard config from above.
+
+
+
+## Credits
+
+This project is inspired by [Svelte MCP server](https://svelte.dev/docs/mcp/overview). Both the implementation and documentation have been adapted and referenced from the original MCP server.
diff --git a/packages/mcp-servers/docs-mcp-server/main.ts b/packages/mcp-servers/docs-mcp-server/main.ts
new file mode 100644
index 0000000000..48b17b50d0
--- /dev/null
+++ b/packages/mcp-servers/docs-mcp-server/main.ts
@@ -0,0 +1,207 @@
+#!/usr/bin/env node
+
+// Copyright 2025 The Lynx Authors. All rights reserved.
+// Licensed under the Apache License Version 2.0 that can be found in the
+// LICENSE file in the root directory of this source tree.
+import { readFile } from 'node:fs/promises';
+
+import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
+import { Command } from 'commander';
+import createDebug from 'debug';
+import * as findPackage from 'empathic/package';
+import type { Link, Node } from 'mdast';
+import { fromMarkdown } from 'mdast-util-from-markdown';
+import { toMarkdown } from 'mdast-util-to-markdown';
+import { fetch } from 'undici';
+
+// NOTE: Un comment below to enable caching and debug for undici fetch requests
+// import {
+// interceptors,
+// EnvHttpProxyAgent
+// setGlobalDispatcher,
+// // @ts-expect-error missing types
+// cacheStores,
+// } from 'undici';
+// const agent = new EnvHttpProxyAgent().compose(
+// interceptors.cache({
+// store: new cacheStores.MemoryCacheStore({
+// maxSize: 100 * 1024 * 1024, // 100MB
+// maxCount: 1000,
+// maxEntrySize: 5 * 1024 * 1024, // 5MB
+// }),
+// methods: ['GET', 'HEAD'], // Optional: specify which methods to cache
+// }),
+// );
+// setGlobalDispatcher(agent);
+
+const debug = createDebug('lynx-docs-mcp');
+
+const pkgPath = findPackage.up({ cwd: new URL('.', import.meta.url).pathname });
+const pkg = JSON.parse(await readFile(pkgPath!, 'utf-8')) as {
+ version: string;
+ name: string;
+ description: string;
+};
+
+const MCP_SERVER_NAME = 'lynx-docs';
+
+function registerResources(
+ baseURL: string,
+ mcpServer: McpServer,
+ fromMarkdownText: string,
+) {
+ const tree = fromMarkdown(fromMarkdownText); // verify markdown is valid
+
+ const forEachLink = (node: Node, cb: (link: Link) => void) => {
+ if (node.type === 'link') {
+ cb(node as Link);
+ } else if ('children' in node && Array.isArray(node.children)) {
+ for (const child of node.children) {
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
+ forEachLink(child, cb);
+ }
+ }
+ };
+
+ const linkUrls: Map = new Map();
+ forEachLink(tree, (link) => {
+ try {
+ const base = new URL(baseURL);
+ const u = new URL(link.url);
+ if (
+ u.hostname === base.hostname
+ // Some links may be absolute URLs to lynxjs.org
+ || u.hostname === 'lynxjs.org'
+ ) {
+ // Strip versioned path prefixes like /next/ or /1.2.3/
+ const strippedUrl = u.pathname.replace(
+ /^\/(?:next|\d+(?:\.\d+)*)?\/?/,
+ '',
+ );
+ linkUrls.set(strippedUrl, link);
+ }
+ } catch {
+ // Ignore invalid URLs
+ }
+ });
+
+ linkUrls.forEach((link, strippedUrl) => {
+ // Generate a title for the resource by converting the link node back to markdown
+ // NOTE: The title generation is complex because link titles may contain nested formatting, DON'T just use link.title
+ const title = toMarkdown({ ...link, type: 'root' }).trim();
+
+ if (!title) {
+ return;
+ }
+
+ debug(
+ `Registering resource: lynx-docs://${strippedUrl} from [${title}](${baseURL}/${strippedUrl})`,
+ );
+
+ mcpServer.registerResource(
+ title,
+ `lynx-docs://${strippedUrl}`,
+ {
+ title,
+ description: title,
+ mimeType: 'text/markdown',
+ },
+ async () => ({
+ contents: [
+ {
+ uri: `lynx-docs://${strippedUrl}`,
+ text: await fetch(link.url).then((res) => res.text()),
+ mimeType: 'text/markdown',
+ },
+ ],
+ }),
+ );
+ });
+}
+
+async function main(baseUrl: string) {
+ const ROOT_DOC_URL = `${baseUrl}/llms.txt`;
+ const ROOT_DOC_MARKDOWN = await fetch(ROOT_DOC_URL).then((res) => res.text());
+ const ROOT_DOC_RESOURCE_URI = 'lynx-docs://llms.txt';
+ const ROOT_DOC_TITLE = 'Read This Before Building with Lynx';
+ const ROOT_DOC_DESCRIPTION =
+ 'Before building with Lynx, be sure to read this document to understand important information and best practices.';
+
+ const mcpServer = new McpServer({
+ name: MCP_SERVER_NAME,
+ version: pkg.version,
+ }, {
+ // NOTE: This instruction for now is not supported by Codex's MCP support,
+ // see https://github.com/openai/codex/issues/6148
+ instructions: `\
+
+For any questions or requirements regarding Lynx:
+
+1. Use the "List Resources Tool" to list all Resources provided in MCP "${MCP_SERVER_NAME}".
+2. First read MCP Resources "${ROOT_DOC_RESOURCE_URI}" (**REQUIRED**), this document is an ENTRYPOINT of all Lynx Docs.
+3. After reading "${ROOT_DOC_RESOURCE_URI}", use the "Read MCP Resources Tool" to retrieve docs you need based on the user's questions or requirements, please read them proactively.
+4. If available, prioritize obtaining Lynx-related information through MCP Resources tools over external web searches.
+
+
+`,
+ });
+
+ mcpServer.registerResource(
+ ROOT_DOC_TITLE,
+ ROOT_DOC_RESOURCE_URI,
+ {
+ title: ROOT_DOC_TITLE,
+ description: ROOT_DOC_DESCRIPTION,
+ mimeType: 'text/markdown',
+ },
+ () => ({
+ contents: [
+ {
+ uri: ROOT_DOC_RESOURCE_URI,
+ text: ROOT_DOC_MARKDOWN,
+ mimeType: 'text/markdown',
+ },
+ ],
+ }),
+ );
+
+ registerResources(baseUrl, mcpServer, ROOT_DOC_MARKDOWN);
+
+ const transport = new StdioServerTransport();
+ await mcpServer.connect(transport);
+}
+
+const program = new Command();
+
+program
+ .name(`npx -y ${pkg.name}`)
+ .description(pkg.description)
+ .option(
+ '--base-url ',
+ 'Base URL for fetching Lynx docs. Set if you want versioned docs.',
+ 'https://lynxjs.org/next/',
+ )
+ .version(pkg.version)
+ .addHelpText(
+ 'after',
+ `
+Usage as a MCP Server:
+ {
+ "mcpServers": {
+ "${MCP_SERVER_NAME}": {
+ "command": "npx",
+ "args": ["-y", "${pkg.name}"]
+ }
+ }
+ }
+`,
+ )
+ .action(async (options: { baseUrl: string }) => {
+ await main(
+ // need to remove trailing slash if any
+ options.baseUrl.replace(/\/+$/, ''),
+ );
+ });
+
+program.parse(process.argv);
diff --git a/packages/mcp-servers/docs-mcp-server/package.json b/packages/mcp-servers/docs-mcp-server/package.json
new file mode 100644
index 0000000000..9634dac857
--- /dev/null
+++ b/packages/mcp-servers/docs-mcp-server/package.json
@@ -0,0 +1,34 @@
+{
+ "name": "@lynx-js/docs-mcp-server",
+ "version": "0.2.1",
+ "description": "A MCP Server providing Lynx documentation resources for LLMs, with carefully designed prompting.",
+ "type": "module",
+ "bin": "./main.ts",
+ "files": [
+ "dist",
+ "main.ts"
+ ],
+ "scripts": {
+ "build": "tsc"
+ },
+ "dependencies": {
+ "@modelcontextprotocol/sdk": "^1.20.0",
+ "commander": "^13.1.0",
+ "debug": "^4.4.3",
+ "empathic": "^2.0.0",
+ "mdast-util-from-markdown": "^2.0.2",
+ "mdast-util-to-markdown": "^2.1.2",
+ "undici": "^6.22.0"
+ },
+ "devDependencies": {
+ "@types/debug": "^4.1.12",
+ "@types/mdast": "^4.0.4",
+ "typescript": "^5.9.3"
+ },
+ "engines": {
+ "node": ">=18.17"
+ },
+ "publishConfig": {
+ "bin": "./dist/main.js"
+ }
+}
diff --git a/packages/mcp-servers/docs-mcp-server/tsconfig.json b/packages/mcp-servers/docs-mcp-server/tsconfig.json
new file mode 100644
index 0000000000..549daeed0e
--- /dev/null
+++ b/packages/mcp-servers/docs-mcp-server/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "../../../tsconfig.json",
+ "compilerOptions": {
+ "allowImportingTsExtensions": false,
+ "isolatedDeclarations": false,
+ "emitDeclarationOnly": false,
+ "types": ["node"],
+ "outDir": "dist",
+ },
+ "include": ["src", "main.ts"],
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 3ee0831cf7..6933e45c35 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -315,6 +315,40 @@ importers:
specifier: ^3.25.76
version: 3.25.76
+ packages/mcp-servers/docs-mcp-server:
+ dependencies:
+ '@modelcontextprotocol/sdk':
+ specifier: ^1.20.0
+ version: 1.20.0
+ commander:
+ specifier: ^13.1.0
+ version: 13.1.0
+ debug:
+ specifier: ^4.4.3
+ version: 4.4.3
+ empathic:
+ specifier: ^2.0.0
+ version: 2.0.0
+ mdast-util-from-markdown:
+ specifier: ^2.0.2
+ version: 2.0.2
+ mdast-util-to-markdown:
+ specifier: ^2.1.2
+ version: 2.1.2
+ undici:
+ specifier: ^6.22.0
+ version: 6.22.0
+ devDependencies:
+ '@types/debug':
+ specifier: ^4.1.12
+ version: 4.1.12
+ '@types/mdast':
+ specifier: ^4.0.4
+ version: 4.0.4
+ typescript:
+ specifier: ^5.9.3
+ version: 5.9.3
+
packages/react:
dependencies:
preact:
@@ -8849,6 +8883,10 @@ packages:
undici-types@7.13.0:
resolution: {integrity: sha512-Ov2Rr9Sx+fRgagJ5AX0qvItZG/JKKoBRAVITs1zk7IqZGTJUwgUr7qoYBpWwakpWilTZFM98rG/AFRocu10iIQ==}
+ undici@6.22.0:
+ resolution: {integrity: sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw==}
+ engines: {node: '>=18.17'}
+
unhead@2.0.17:
resolution: {integrity: sha512-xX3PCtxaE80khRZobyWCVxeFF88/Tg9eJDcJWY9us727nsTC7C449B8BUfVBmiF2+3LjPcmqeoB2iuMs0U4oJQ==}
@@ -18369,6 +18407,8 @@ snapshots:
undici-types@7.13.0: {}
+ undici@6.22.0: {}
+
unhead@2.0.17:
dependencies:
hookable: 5.5.3