こんにちは、CTOの小笠原(@yamitzky)です。
NewsDigestでは、アプリ内のお知らせなどに使っている WordPressサーバーがありました。先日、このWordPressの廃止と、自前CMSへのリプレイスを行いました。
理由としては、
- 要件が少なくプラグインも使っていなかったためWordPressである必要がない
- 社内の標準技術から外れておりメンテナンスが難しい
などです。
この記事では、その技術スタックと、MCPサーバーなどを活用した「新しいCMSの形」を模索してみた話を共有します。
伝えたいこと
記事が長くなってしまったので、最初に伝えたいことをまとめます。
- Cloudflare AccessとHonoで簡単に認証付きMCPサーバーが作れる
- 認証付きMCPサーバーはヘッドレスな管理画面になる
- AI時代のCMSの形は、一人ひとりやチームに適したCMSとそれを支えるMCPサーバーではないか(提案)
背景
移行対象は、VPSで動いていたWordPressのサイトです。 当該WordPressで配信される記事は、数行のリリースノートのような短い記事がほとんどで、追加で固定ページが10件ちょっとあるようなサイトです。
CMSとしての要件は、記事と固定ページのCRUD、画像アップロード、少人数の編集者による下書きと公開です。簡易的なお知らせが主用途で、WordPressプラグインなども使っていませんでした。この程度であればAIを活用すれば開発は容易です。
CMSの要件
今回新しく作る内製CMSの要件として、追加で以下のものを定めました。
- 一般的な記事のCRUD操作、公開・非公開設定ができること
- WYSIWYGのエディターがあり、あらゆる職種の人が直感的に操作できること
- ユーザー管理がGoogle Workspaceや、グループと連携していること
- 公開サイトと管理用サイトが独立しており、社外の人が管理用サイトにアクセスできないこと(ヘッドレスCMS)
- ClaudeなどのAIツールからアクセスできること(MCP対応)
太字をつけたところを少し解説します。
WordPressのCMSは、デフォルトでは公開サイト内の /wp-admin/ からアクセスできてしまいますし、ユーザー管理も社内のID基盤とはつながっていません。プラグインを入れるなどしてWordPressのセキュリティを向上することもできますが、今回はCloudflare Accessを導入してヘッドレスCMS*1として作ることで、マネージドで、根本からセキュアな記事管理環境を作りました。
また、普通のCMSでは、管理用サイト(WordPressでいうwp-admin)を開いて、人が記事を書きます。しかし、JX通信社では業務にClaudeを活用しています。そのため、ClaudeにMCPをつなぎこむことで、わざわざ人間が管理画面を開かず、AIに更新してもらえるような仕組みを目指しました。

とはいえ、管理画面経由で開きたいこともあると思うので、念の為WYSIWYGの編集画面もTiptapで用意しています。
技術スタックの全容
| 役割 | 技術 |
|---|---|
| 公開サイト | Next.js(App Router、Cloud RunでSSR) |
| 管理画面 | React + Vite + Tiptap(リッチテキストエディタ) |
| API / MCPサーバー | Hono |
| データベース | Firestore |
| 画像ストレージ | Cloud Storage |
| 実行基盤 | Cloud Run |
| 公開サイトのCDN | Firebase Hosting |
| 管理画面とMCPの認証 | Cloudflare Access |

アクセスの経路は、公開サイトと管理画面で完全に分かれています。
公開サイトはFirebase HostingをCDNとしてCloud Runのwebを配信し、認証はかけません。
管理画面とMCPのドメインはCloudflareが前段に立ち、Cloudflare Accessで保護します。
アプリケーションはすべてGoogle Cloudにあり、Cloudflareは認証だけしています*2。
Cloudflare Accessでの認証
Google Cloud Runのサービスを認証付きで保護する場合、普通はGoogle Cloudの Identity-Aware Proxy (IAP)を使うのが一般的でしょう。しかし、今回は、Cloudflare Accessを選定しました。IAPだけではMCPの認証(OAuth 2.1の認可フロー)を提供することができないためです。
Cloudflare Accessは、Cloudflareが提供するゼロトラストソリューションです。ものすごく省略して言えば「Google WorkspaceなどのIdPと連携して、社員限定に保護された環境を提供してくれるプロキシー」といったところでしょうか。
Accessには「Managed OAuth」という機能があり、これを有効にするだけで、MCPクライアントをOAuth 2.1の認可コードフローで受けられます。Google WorkspaceをIdPとして連携すると、「このGoogleグループのメンバーだけ許可」というポリシーを、ブラウザのSSOとMCPクライアントのOAuthの両方に共通で適用することもできます。

この構成にすると、オリジン側(Cloud Run)の仕事はJWTの検証だけになります。Cloud Run自体は --allow-unauthenticated でデプロイし、Google CloudのIAMでは認証しません。
Cloudflare Accessが認証を済ませたリクエストには Cf-Access-Jwt-Assertion ヘッダーで検証済みJWTが付いてくるので、アプリはその署名と発行者を確認するだけです。
HonoでMCPサーバーを書く
バックエンド(API、MCP)にHonoを選んだのは、この構成に必要な部品が揃っていたからです。
- @hono/cloudflare-access:
Cf-Access-Jwt-AssertionのJWT検証を行う - @modelcontextprotocol/server:MCP公式TypeScript SDK(v2)。Web標準の
Request/Responseで動くので、Honoにそのまま載る
MCPサーバーの骨格は、SDK v2の createMcpHandler を使えばこれだけで書けます。渡したファクトリ関数がリクエストごとに呼ばれて McpServer を組み立てる作りなので、「認証された本人によってふるまいを変える」が素直に書けます((起動時に単一の McpServer を作って全員で共有する、より簡単な構成もあります。Hono作者の和田さんが紹介していた形です。今回は相手ごとにツールを出し分けたいので、リクエストごとに組み立てています))。
import { cloudflareAccess } from "@hono/cloudflare-access"; import { createMcpHandler } from "@modelcontextprotocol/server"; const app = new Hono(); // 全ルートに Cloudflare Access の JWT 検証をかける app.use("*", cloudflareAccess(process.env.CF_ACCESS_TEAM, process.env.CF_ACCESS_AUD)); // MCP エンドポイント。リクエストごとに、認証済みユーザーのツール集合でサーバーを組む app.all("/mcp", (c) => createMcpHandler(() => buildMcpServer(c.get("accessPayload").email)).fetch(c.req.raw), );
createMcpHandler が返すのはWeb標準のfetchハンドラです。Honoからは c.req.raw を渡すだけです。
app.use("*") で全体に認証をかけているので、REST APIも /mcp も同じ認証境界の内側にあります。また、JWTから取り出したemailをツールの実行者として渡せば、AIエージェント経由の変更にも「誰の認証で実行されたか」を更新履歴に残すことができます。
サンプル中の buildMcpServer が「認証された相手ごとのサーバーを組み立てる」処理の本体です。ツールは記事とページのCRUD、画像アップロードなど12個で、@modelcontextprotocol/server とZodで定義しています。中身を要点だけ抜き出すとこうなります。
// 認証された相手に応じて、見せてよいツールだけを登録した McpServer を組み立てる function buildMcpServer(email: string) { const server = new McpServer({ name: "newsdigest-landing-page", version: "1.0.0" }); server.registerTool( "create_post", { description: "記事を新規作成する。本文はMarkdownで渡す", inputSchema: z.object({ title: z.string(), slug: z.string(), body: z.string(), categories: z.array(z.string()), status: z.enum(["draft", "published"]), }), }, (args) => handlers.create_post(args, email), // email が更新履歴に実行者として残る ); // ... 同様に、記事・固定ページのCRUDや画像アップロードなど計12ツールを登録 return server; }
MCPはヘッドレスな管理画面になる
CMSのCRUD操作を認証付きのMCPとして用意しておくと、
- Slack上で、Claudeに依頼して記事を更新
- Claude Artifact上でカスタム管理画面をさっと用意する
などができます。管理画面の機能が、「従来のWYSIWYGエディター」というUIに縛られず、任意のUI(チャットやArtifact)に差し替えられるわけですから、まさに「ヘッドレスな管理画面」と言えるのではないでしょうか。
これらの具体例を紹介します。
Slack上での記事更新
JX通信社では、Claude Tagを使ってSlack上からClaudeを呼び出せるようにしています。このCMSのMCPを繋いでおけば、Slackで「今週のリリースノートを下書きして」と頼むだけで、ClaudeがMCPの create_post を呼び、下書きが保存されます。人がやるのは、できあがった下書きを確認して公開に切り替えることだけです。

Claude Artifactでのカスタム管理画面
もうひとつの入り口が、Claude Artifactです。Artifactは、Claude CodeからインタラクティブなHTMLを出力し、claude.ai上でチームなどに共有できる機能で、2026年7月から、MCPコネクタを呼び出せるようになりました。
ポイントは、ArtifactからのMCP呼び出しが「閲覧者自身の認証権限」で行われることです。Artifactのページ自体は資格情報を持たず、claude.aiが閲覧者のコネクタで代理リクエストします。流れを図にするとこうなります。

これを使って、Claude Codeに「リリースノートの作成に特化した管理画面をArtifactで作って」と頼んでみました。

このArtifactは汎用の管理画面ではありません。
リリースノートを書くというひとつの用途のためだけの、自分の作業に最適化されたUIです。
汎用の管理画面は全員のための最大公約数になりがちですが、CMSがMCPを提供していれば、各自が自分の用途に合わせた「俺だけの管理画面」をAIに作らせることができます。もちろん、チームの業務に特化した管理画面を作って、チーム内で共有することもできます。
ハマったところ
claude.aiが繋がらない
Claude Code 経由では簡単にMCPに接続することができたのですが、claude.aiのカスタムコネクタとしてMCPのURLを登録しても、接続が失敗してしまいました。
原因としては、AccessのMCPサーバーアプリにある「Allowed redirect URIs」に https://claude.ai/api/mcp/auth_callback を登録していなかったことでした。
Claude Tag の認証
Claude Tag(SlackからClaudeを呼び出せる機能)では、Managed OAuthの認証経由でのアクセスはできませんでした。そのため、CloudflareのService Tokenでの認証をしています。
おわりに
管理画面を作り込む代わりにMCPを1本生やしておくと、ClaudeやCodexなどのチャットも、Artifactも、管理画面になります。
なかでもArtifactとの組み合わせは、「管理画面を作って配る」から「使う人が自分の管理画面をAIに作らせる」への転換で、CMSに限らず社内ツール全般に効く話だと思っています。
HonoとCloudflare Accessを使えば、認証付きMCPサーバー自体は簡単に作れるので、管理画面つきのWebサービスを持っている方は、ぜひ検討してみてください。






