MCPサーバーでMarkdown知識ベースを配信する設計: HTMLに変換しない理由
Markdownで書いた知識ベースを、WebサイトにもAIコーディングツールにも読ませたいことがあります。
このとき迷いやすいのが、MCPサーバーから何を返すかです。MarkdownをHTMLへ変換して返すべきか、それともMarkdownのまま返すべきか。
結論から言うと、知識ベース用途ではMCPサーバーはraw Markdownを返し、HTML変換はWeb側に任せる設計が扱いやすいです。MCP(Model Context Protocol)は、AIアプリケーションが外部のデータやツールに接続するための標準プロトコルです。表示形式までMCPサーバーに背負わせると、最初は便利でも、クライアントが増えたときに保守が重くなります。
この記事では、実際にMarkdown記事をMCPサーバーから配信できることを確認したうえで、なぜHTMLに変換しない設計がよいのかを整理します。
確認したこと
ローカルのMarkdown知識ベースに対して、stdio transportのMCPサーバーを起動し、MCP SDKのクライアントから次の3つのツールが見えることを確認しました。
list_articles
get_article
search_knowledge
さらに get_article を呼び出すと、記事のmetadata(タイトル、日付、slug、tagsなど)と本文がJSONで返りました。本文はHTMLではなくMarkdownです。検証時点では、取得した本文に <h1>、<h2>、<p> のようなHTMLタグは含まれず、Markdownの本文として扱える状態でした。
つまり、「Markdownファイルを1つ置き、それをMCPサーバーからAIクライアントへ配信する」構成は実際に成立します。
推奨設計
おすすめの構成は、次のように責務を分ける形です。
content/*.md
-> MCP server: raw Markdownを返す
-> Next.js site: MarkdownをHTMLへ変換して表示する
-> AI coding tool: Markdownをそのまま読む
MCPサーバーは、Markdownファイルを読み、frontmatter(記事のメタ情報)と本文を返すだけにします。Webサイト側は、remark などのMarkdown処理ライブラリでHTMLへ変換します。AIコーディングツールやエディタ拡張は、Markdownのまま読めます。
この分離のポイントは、MCPサーバーを「表示エンジン」ではなく「知識の配信口」にすることです。
なぜHTMLを返さないのか
HTMLを返すMCPサーバーも作れます。Webサイトだけがクライアントなら、それでも動きます。
ただし、MCPの用途ではHTMLが常に最適とは限りません。Claude CodeのようなAIコーディングツールは、HTMLよりMarkdownのほうが読みやすく、引用もしやすいです。モバイルアプリやデスクトップアプリに展開する場合も、HTMLをそのまま表示するより、ネイティブUIや独自コンポーネントへ変換したい場面があります。
MCPサーバーがHTMLを返すと、サーバーがWeb表示の都合に引きずられます。
- 見出し構造を変えるたびにMCP側の変換ロジックが影響を受ける
- Web用CSSに依存したHTMLがAIクライアントへ渡る
- 別クライアント向けに再変換が必要になる
- Markdown本文よりトークン量が増えやすい
知識ベースの原本がMarkdownなら、MCPはその原本を返すほうが自然です。
実装の形
最小構成では、MCPサーバーに3つのツールを用意します。
list_articles()
get_article(slug, lang)
search_knowledge(query)
list_articles() は一覧表示や検索候補に使うため、本文を返さずmetadataだけを返します。たとえば slug、title、date、description、tags、lang です。
get_article(slug, lang) は、指定された記事のmetadataとMarkdown本文を返します。ここでHTMLへ変換しないことが重要です。
search_knowledge(query) は、タイトル、説明文、本文に対して簡単な検索を行い、該当記事のmetadataを返します。全文を毎回返さないことで、AIクライアント側でも必要な記事だけを追加取得できます。
疑似コードにすると、MCP側の考え方は次のようになります。
type Article = {
slug: string;
title: string;
date: string;
description: string;
tags: string[];
lang: "en" | "ja";
body: string; // raw Markdown
};
function getArticle(slug: string, lang: "en" | "ja"): Article | null {
const file = findMarkdownFileBySlug(slug, lang);
if (!file) return null;
const { data, content } = parseFrontmatter(file);
return {
slug: data.slug,
title: data.title,
date: data.date,
description: data.description,
tags: data.tags ?? [],
lang: data.lang ?? "en",
body: content,
};
}
一方、Webサイト側では同じMarkdown本文をHTMLへ変換します。
import { remark } from "remark";
import html from "remark-html";
export async function renderArticleHtml(markdown: string): Promise<string> {
const processed = await remark().use(html).process(markdown);
return processed.toString();
}
このように分けると、MCPサーバーはMarkdownの読み出しと検索に集中できます。表示の都合は、それぞれのクライアントが持てばよいです。
frontmatterは共通契約にする
Markdown知識ベースでは、frontmatterをクライアント間の共通契約にすると安定します。
---
title: "Article title"
date: "2026-06-23"
slug: "article-slug"
description: "Short description for lists and metadata."
tags: ["mcp", "markdown"]
lang: "ja"
---
Article body...
Webサイトは title をページタイトルやH1に使えます。MCPサーバーは slug を記事取得のキーにできます。description と tags は検索結果や記事一覧に使えます。
ここで注意したいのは、frontmatterの解釈をWeb側とMCP側で揃えることです。たとえば draft: true の記事をWebサイトでは非公開にしているのに、MCPサーバーでは返してしまうと、公開前の情報がAIクライアントに見えてしまいます。
同じMarkdownを複数の読み手で使うなら、次のルールは揃えておくべきです。
- 必須項目:
title、date、slug、description draft: trueはすべての公開面から除外するlangで言語を分けるslugはURLとMCP取得キーの両方で使う- tagsは小文字のkebab-caseに揃える
静的サイトとの相性
この設計は、Next.jsの output: export のような静的書き出しとも相性がよいです。
Webサイトはビルド時にMarkdownをHTMLへ変換し、静的HTMLとして配信できます。MCPサーバーはローカル開発環境や社内ツール側でMarkdownを直接読みます。読者向けのWeb配信にはデータベースも実行時APIも不要です。
Next.jsの静的書き出しでは、配信時にNode.jsサーバーが存在しません。そのため、RSS、sitemap、記事HTMLはビルド時に確定させる必要があります。この点は別記事の Next.jsのoutput: exportで詰まる5つの罠と回避策 で詳しく整理しています。
また、開発中に next build は通るのに next dev だけCSSで壊れる場合は、MCPやMarkdown設計ではなく環境変数の問題かもしれません。該当する場合は next devだけCSSが壊れる原因はNODE_ENV=productionだった も確認してください。
セキュリティと公開範囲
Markdown知識ベースをMCPで配るときは、公開範囲を明確にします。
特にローカルMCPサーバーは便利ですが、接続したAIクライアントにMarkdown本文を渡します。記事内に秘密情報、内部URL、個人情報、APIキー、顧客名、未公開の事業情報が入っていれば、そのままコンテキストとして渡る可能性があります。
最低限、次をチェックしてください。
content/に秘密情報を置かないdraft: trueをMCP側でも除外するCONTENT_DIRのような設定で、読むディレクトリを明示する- パス指定を受け取る設計にする場合は、slugから安全に解決する
- stdoutはMCPのJSON-RPC通信に使うため、ログはstderrへ出す
MCPは外部データをAIに接続するための仕組みです。便利さと同時に、「何を読ませるか」を設計上の責務として扱う必要があります。
まとめ
Markdown知識ベースをMCPサーバーで配信するなら、MCP側でHTMLへ変換しない設計が保守しやすいです。
MCPサーバーはraw Markdownとmetadataを返す。WebサイトはMarkdownをHTMLへ変換する。AIコーディングツールはMarkdownをそのまま読む。この分担にすると、1つのMarkdown原稿を複数の読み手で共有できます。
実機確認でも、MCPクライアントから list_articles、get_article、search_knowledge を呼び出し、get_article がHTMLではないMarkdown本文を返すことを確認できました。
MCPサーバーを「何でも変換するAPI」にするのではなく、「原本を安全に渡す薄い配信レイヤー」にする。Markdown知識ベースでは、このくらい単純な設計のほうが長く使えます。