Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 31, 2026

Implementation Plan for pmndrs "Federated AI Docs" Initiative

  • 1. Add <page> tag wrapping in llms-full.txt route

    • Update /src/app/llms-full.txt/route.ts to wrap each doc in <page path="..." title="..."> tags
    • Use xmlbuilder2 library with fragment mode for efficient XML generation
    • Verified TypeScript compilation passes
    • Tested page tag format with Cheerio parsing
  • 2. Create MCP server implementation

    • Create /src/app/mcp/route.ts using mcp-handler for Vercel
    • Import libs directly from page.tsx without adapter layer
    • Implement list_pages and get_page_content tools
    • Support both external URLs and internal routes (via docs.pmnd.rs)
    • Server name set to "pmndrs-docs" in serverInfo
    • Simple, clean implementation without conditional logic
    • Use native Node.js fetch (no node-fetch dependency)
    • Simple shell-based prebuild script
    • Fix CSS selector injection vulnerabilities
    • Optimize with constants extraction
    • Verified syntax and imports
    • Fixed import path and TypeScript errors
    • Fixed static export compatibility
  • 3. Update package.json and pnpm-lock.yaml

    • Add required dependencies (mcp-handler, zod, xmlbuilder2)
    • Remove node-fetch (use native Node.js fetch)
    • Upgrade MCP SDK to secure version (1.25.3)
    • Update pnpm-lock.yaml to sync with package.json changes
    • Simplified prebuild script using plain shell commands
  • 4. Documentation and cleanup

    • Remove IMPLEMENTATION_SUMMARY.md (redundant with PR description)
    • Remove MCP_README.md
    • Add MCP section to docs/getting-started/introduction.mdx
    • Remove hardcoded library list (avoid maintenance burden)
    • Remove duplicate URL mappings
    • Clarify curl examples with SSE flag
    • Format code with prettier
    • Verify TypeScript compilation
    • Security review - no vulnerabilities found
    • CodeQL analysis - 0 alerts
    • Code review completed and addressed
    • CI passing with static export mode

Summary

Final working implementation:

Priority #1: Page tag wrapping with proper XML library (xmlbuilder2 fragment mode)
✅ MCP server with list_pages and get_page_content tools using mcp-handler
✅ Direct use of libs object from page.tsx (no adapter layer, no duplicate mappings)
✅ Support for both external URLs and internal routes via docs.pmnd.rs base
✅ Server name: "pmndrs-docs" (v1.0.0)
/mcp HTTP/SSE endpoint optimized for Vercel deployment
Native Node.js fetch - no external dependencies for HTTP calls
Simple shell prebuild - one-line command instead of Node.js script
✅ Simple, clean MCP route implementation
✅ Static export compatibility via prebuild script that removes /mcp in export mode
✅ Full MCP functionality in server deployments (Vercel, etc.)
✅ XML escaping and generation handled by proper library
✅ Documentation integrated into main docs without hardcoded lists
✅ All security checks passed
✅ MCP SDK upgraded to secure version 1.25.3
✅ CI builds successfully in both server and export modes
✅ pnpm-lock.yaml fully synchronized with package.json

Key Dependencies:

  • @modelcontextprotocol/sdk@^1.25.2 - MCP protocol (secure version)
  • mcp-handler@^1.0.7 - Vercel MCP adapter
  • zod@^3.24.1 - Type-safe schemas
  • xmlbuilder2@^3.1.1 - Proper XML generation
  • cheerio@^1.0.0 - XML parsing
  • Native fetch - No external HTTP library needed (Node.js >=20.9.0)

Solution for Static Export Compatibility

  • MCP Route: Simple, straightforward implementation using mcp-handler
  • Prebuild Script: One-line shell command in package.json that removes the /mcp directory when OUTPUT=export is set
    • test "$OUTPUT" = "export" && rm -rf src/app/mcp || true
    • No Node.js script needed - pure shell command
    • Works on all Unix-like systems

This provides clean separation:

  • Static export builds (GitHub Pages) succeed without the dynamic route
  • Server deployments (Vercel) include full MCP functionality
  • No conditional logic needed in the route itself

Usage

Remote Access

# Claude Desktop config
{
  "mcpServers": {
    "pmndrs": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://docs.pmnd.rs/mcp"]
    }
  }
}

Agents can now:

  1. Connect remotely via Streamable HTTP or SSE
  2. Fetch /llms-full.txt from any of 13 registered pmndrs libraries
  3. Parse <page> tags to list available paths
  4. Extract specific page content without downloading entire sites

See the MCP section in the documentation for integration details.

Original prompt

Brief: The pmndrs "Federated AI Docs" Initiative
🎯 The Vision
We are transforming the pmndrs documentation (R3F, Zustand, Drei, etc.) into an AI-native ecosystem. Instead of having agents crawl messy HTML, we provide structured, federated entry points via llms.txt and MCP (Model Context Protocol).
The end goal is a centralized CLI tool: npx @pmndrs/docs mcp. This tool allows any AI agent (Cursor, Windsurf, Claude) to surgically query any library in the pmndrs galaxy.
🏗️ Current State & Infrastructure

  • Next.js Routes: The endpoints /llms.txt and /llms-full.txt are already implemented across the various library doc sites.
  • Federated Domains: Knowledge is spread across subdomains (e.g., r3f.docs.pmnd.rs) and GitHub Pages (e.g., pmndrs.github.io/viverse).
  • Missing Link: The llms-full.txt files currently lack internal delimitation. You need to implement the tag wrapping logic in the static generator to allow surgical extraction.
[Clean Markdown Content]

🛠️ The Reference MCP Implementation
Below is a non-prescriptive starter for the MCP server. Use it as a foundation to build the npx @pmndrs/docs mcp command. It uses Cheerio for robust parsing.
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
import fetch from "node-fetch";
import * as cheerio from "cheerio";

// This registry (from page.tsx) is your map to the federated domains
const REGISTRY = {
'react-three-fiber': { url: 'https://r3f.docs.pmnd.rs' },
'zustand': { url: 'https://zustand.docs.pmnd.rs' },
'drei': { url: 'https://drei.docs.pmnd.rs' },
'viverse': { url: 'https://pmndrs.github.io/viverse' }
};

const server = new Server({ name: "pmndrs-mcp", version: "1.0.0" }, { capabilities: { tools: {} } });

server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "list_pages",
description: "List all available paths for a pmndrs library.",
inputSchema: { type: "object", properties: { lib: { type: "string", enum: Object.keys(REGISTRY) } }, required: ["lib"] }
},
{
name: "get_page_content",
description: "Get surgical content of a specific page.",
inputSchema: { type: "object", properties: { lib: { type: "string", enum: Object.keys(REGISTRY) }, path: { type: "string" } }, required: ["lib", "path"] }
}
]
}));

server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
const config = REGISTRY[args.lib];
const response = await fetch(${config.url}/llms-full.txt);
const fullText = await response.text();
const $ = cheerio.load(fullText, { xmlMode: true });

if (name === "list_pages") {
const paths = $("page").map((_, el) =&gt; $(el).attr("path")).get();
return { content: [{ type: "text", text: paths.join("\n") }] };
}

if (name === "get_page_content") {
const page = $(page[path="${args.path}"]);
return { content: [{ type: "text", text: page.text().trim() }] };
}
});

const transport = new StdioServerTransport();
await server.connect(transport);

🚀 Future Evolutions: AI Skills
We want to move beyond raw API data.

  • Library Skills: Each lib should eventually expose a /skills/ directory containing skill.md files (best practices, architectural patterns).
  • Cross-Lib Skills: The MCP server could offer a "Cross-Lib" tool to explain how to combine libraries (e.g., "Best practices for using Zustand with R3F").
  • Universal Discovery: The CLI should eventually auto-discover new libraries added to the registry without code changes.
    💡 Guidelines for the Agent
  • Be Surgical: Never dump the whole llms-full.txt if the user only needs one hook.
  • Respect the Registry: Always use the URLs defined in the central Record<string, Library>.
  • Delimiters are Priority Link not working #1: Your first task is ensuring the static generator correctly wraps MDX content in tags.

💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 5 commits January 31, 2026 09:55
Co-authored-by: abernier <76580+abernier@users.noreply.github.com>
Co-authored-by: abernier <76580+abernier@users.noreply.github.com>
Co-authored-by: abernier <76580+abernier@users.noreply.github.com>
Co-authored-by: abernier <76580+abernier@users.noreply.github.com>
Co-authored-by: abernier <76580+abernier@users.noreply.github.com>
Copilot AI changed the title [WIP] Implement page tag wrapping for llms-full.txt files Jan 31, 2026
Copilot AI requested a review from abernier January 31, 2026 10:04
@abernier
Copy link
Member

@copilot also expose a /mcp sse route for remote mcp server

Co-authored-by: abernier <76580+abernier@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Jan 31, 2026

@copilot also expose a /mcp sse route for remote mcp server

Added /mcp SSE route in commit 6447d62. The endpoint supports both GET (Server-Sent Events) and POST (direct HTTP) for remote MCP server access. Updated MCP_README.md with remote usage examples.

Co-authored-by: abernier <76580+abernier@users.noreply.github.com>
Copilot AI changed the title Add MCP server and page tag delimiters for surgical doc queries Jan 31, 2026
@abernier
Copy link
Member

@copilot ok j'ai changé d'avis: on va se contenter du server mcp distant sur la route /mcp dans un premier temps ca simplifiera

stp tu peux supprimer le bin/mcp.mjs et tout le reste afférent

Co-authored-by: abernier <76580+abernier@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Jan 31, 2026

@copilot ok j'ai changé d'avis: on va se contenter du server mcp distant sur la route /mcp dans un premier temps ca simplifiera

stp tu peux supprimer le bin/mcp.mjs et tout le reste afférent

Removed bin/mcp.mjs and stdio configuration in commit 19a906b. The implementation now focuses solely on the remote HTTP/SSE endpoint at /mcp. Updated MCP_README.md and IMPLEMENTATION_SUMMARY.md to reflect this simplified approach.

Copilot AI changed the title Add MCP server with dual transport (stdio + HTTP/SSE) for surgical doc queries Jan 31, 2026
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored in commit 49212f3. The registry now dynamically derives from the libs object in page.tsx, eliminating duplication. This expands MCP server support from 4 to 13 pmndrs libraries: react-three-fiber, react-spring, drei, zustand, jotai, valtio, a11y, react-postprocessing, uikit, xr, prai, viverse, and leva.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right - removed the adapter layer in commit 4ff8a44. The MCP route now directly imports and uses libs from page.tsx. URL mapping logic is inline, eliminating the intermediate registry.ts file entirely (-79 lines).

Copilot AI and others added 2 commits January 31, 2026 10:47
Co-authored-by: abernier <76580+abernier@users.noreply.github.com>
Co-authored-by: abernier <76580+abernier@users.noreply.github.com>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot tu as dupliqué énormément de logique déja presente dans src/app/mcp/route.ts qd je t'avais demander de la partager/réutiliser...

Copilot AI and others added 2 commits February 1, 2026 11:52
Co-authored-by: abernier <76580+abernier@users.noreply.github.com>
Co-authored-by: abernier <76580+abernier@users.noreply.github.com>
Copilot AI changed the title Add MCP server with remote (HTTP/SSE) and local (offline) access for surgical doc queries Feb 1, 2026
Copilot AI requested a review from abernier February 1, 2026 11:57
@abernier
Copy link
Member

abernier commented Feb 1, 2026

@copilot reviens a 16b4878

Co-authored-by: abernier <76580+abernier@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 1, 2026

@copilot reviens a 16b4878

Reverted to commit 16b4878 in e97677f. Removed local MCP server (bin/mcp-local.*), shared logic module (src/lib/mcp-tools.ts), and related documentation. The implementation now focuses solely on the remote HTTP/SSE MCP server at /mcp route using mcp-handler.

Copilot AI changed the title Add TypeScript MCP server with remote (HTTP/SSE) and local (offline) access for surgical doc queries Feb 1, 2026
package.json Outdated
"scripts": {
"dev": "NODE_OPTIONS='--inspect' next",
"start": "next start",
"prebuild": "node scripts/prebuild.mjs",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot cant we just rm in plain shell ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplified to plain shell command in commit 1618bbd. Replaced the Node.js script with a one-liner: test "$OUTPUT" = "export" && rm -rf src/app/mcp || true

Co-authored-by: abernier <76580+abernier@users.noreply.github.com>
Co-authored-by: abernier <76580+abernier@users.noreply.github.com>
@abernier
Copy link
Member

abernier commented Feb 1, 2026

@copilot ci fails, see logs

Co-authored-by: abernier <76580+abernier@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 1, 2026

@copilot ci fails, see logs

Fixed in commit 84aac04. The CI was failing because pnpm-lock.yaml wasn't updated when node-fetch was removed from package.json. Removed the node-fetch specifier entry from pnpm-lock.yaml to sync with package.json changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants