Vitepress ドキュメントを MCP 化する
はじめに
今回の実装は mastra というフレームワークの初期化時に追加できる MCP のドキュメントサーバーがめちゃくちゃ便利だったので、それに影響を受けて手元の Vitepress 製のドキュメントを MCP (Model Context Protocol) 化した際の記録です。
今回は自分の手元の Vitepress 製のドキュメントを MCP 経由で取得できるようにしてみました。
環境情報
今回使用した環境は以下の通りです。
- Node.js: v20.17.1
- pnpm: 9.11.5
- VitePress: 1.6.3
準備
パッケージのインストール
Vitepress プロジェクトにパッケージを追加します。 fastmcp
というパッケージを使うと MCP サーバーを簡単に作れて便利でした。
# MCP関連
pnpm add fastmcp node-html-markdown zod
# TypeScript関連
pnpm add @types/node typescript
package.jsonの設定
package.json のtype
をmodule
に設定します。package のパスを取得するために必要でした。
{
"type": "module",
// ...その他の設定
}
その他は、必要に応じて tsconfig.json
を追加します。これで環境構築は完了です。
Vitepress のビルド
次に、Vitepress をビルドして配信用の HTML ファイルを生成します。
# `vitepress build` が実行されます。
pnpm run docs:build
MCP サーバーの実装
.vitepress/dist
以下の HTML ファイルを Markdown に変換して提供する MCP サーバーを実装します。
今回は、dist 直下のディレクトリを tool の単位することにしました。(例: basic/, advanced/ など)
MCP のクライアントが basic/
を要求すると basic/
以下のドキュメントのパスを一覧として返却し、basic/getting-started.html
などのパスを要求するとドキュメントを返します。
以下が server.ts の全コードです。
import { FastMCP } from 'fastmcp';
import { NodeHtmlMarkdown } from 'node-html-markdown';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'url';
import { z } from 'zod';
const nhm = new NodeHtmlMarkdown();
// パッケージのルートディレクトリを取得する関数
function getPackageRoot(): string {
// ESM環境では __dirname が使えないため、import.meta.url から解決
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
return path.resolve(__dirname);
}
function getTopPaths(): string[]{
const distPath = path.join(getPackageRoot(), '.vitepress', 'dist');
const dirEntries = fs.readdirSync(distPath, { withFileTypes: true });
return dirEntries.filter(entry => entry.isDirectory() && entry.name !== 'assets').map(entry => entry.name);
}
function _getDocuments(dir: string): string[] {
const result: string[] = [];
function traverseDirectory(currentDir: string) {
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(currentDir, entry.name);
if (entry.isDirectory()) {
traverseDirectory(fullPath);
} else {
result.push(fullPath);
}
}
}
traverseDirectory(dir);
return result;
}
function getDocuments(dir: string): string[] {
const distPath = path.join(getPackageRoot(), '.vitepress', 'dist');
const dirPath = path.join(distPath, dir);
const documents = _getDocuments(dirPath);
return documents.map(document => document.replace(distPath, ''));
}
function getDocument(url: string): string {
const distPath = path.join(getPackageRoot(), '.vitepress', 'dist');
const documentPath = path.join(distPath, url);
const doc = fs.readFileSync(documentPath, 'utf-8');
return nhm.translate(doc);
}
const server = new FastMCP({
name: 'ドキュメントサーバー',
version: '1.0.0',
});
getTopPaths().forEach(path => {
server.addTool({
name: path,
description: `Get ${path} content. Without a URL, returns a list of all ${path} content.With a URL, returns the specific ${path} post content in markdown format.`,
parameters: z.object({
url: z
.string()
.describe(
`URL of a specific ${path} post to fetch. If the string /${path} is passed as the url it returns a list of all ${path} documents.`,
),
}),
execute: async (params) => {
if (params.url === `/${path}`) {
return JSON.stringify({
documents: getDocuments(path),
});
} else {
return JSON.stringify({
document: getDocument(params.url),
});
}
},
});
});
// サーバー起動
server.start({
transportType: 'stdio', // CLIからの起動用
});
MCP サーバーの登録
ここまで実装できたら、あとは MCP サーバーを Cursor など MCP クライアントとして使えるアプリケーションに登録します。以下は Cursor に設定した場合な mcp.json の例です。
/${DOCS_ROOT}/server.ts
には先ほど実装した server.ts の絶対パスを指定します。
{
"mcpServers": {
"cline-docs": {
"command": "pnpx",
"args": [
"tsx",
"/${DOCS_ROOT}/server.ts"
],
"env": {},
"disabled": false,
"autoApprove": [
"getCategories",
"getDocument",
"searchDocuments"
]
}
}
}
動作確認
これで設定は完了で、Cursor Agent から MCP 経由で Vitepress のドキュメントを取得できるようになりました。
まとめ
Vitepress が静的 HTML を吐いてくれるので、お手軽に MCP 化に出来るのがかなりいいと思いました。社内ドキュメントなどで Vitepress を使っている組織も多いと思うのでオススメです。
ただ、もっとスマートなやり方があると思うので、試した方はご共有いただけるとありがたいです。
Discussion