🔌

Movable Type を MCP で操作する mt-plugin-mcp を作った話

に公開

概要

SaaS を MCP でつなぐ事例が増えてきました。需要の有無は別として、Movable Type(MT)のような CMS でも同じことができないかと考え、MT 9 向けの MCP サーバープラグイン mt-plugin-mcp を作りました。

https://github.com/redamoon/mt-plugin-mcp

MT にはもともと Data API があり、外部サービスとの連携を前提に設計されています。AI エージェントが API 経由で登録や分析を担う場面が増えた今、その流れの中に MCP も位置づけられるのではないかと感じています。

本プラグインを使うと、Cursor から MT の記事やコンテンツデータを自然言語で追加できます。本記事では、作った背景、プラグインの構成、Cursor からの接続方法、実際に試して分かったハードケースまでをまとめます。

なぜ MCP プラグインを作ったか

MT の操作手段はいくつかあります。

  • 管理画面から手動で編集する
  • Data API で REST 的に CRUD する
  • Perl プラグインで CMS 内部のオブジェクトを直接触る

AI エージェントに任せる場合、3つ目の Perl API をそのまま使わせるのはハードルが高いです。
一方で Data API だけ渡しても、エンドポイント・認証・リクエスト形式の説明が毎回必要になります。

MCP は「ツール一覧 + スキーマ + 呼び出し」という形でエージェントに能力を渡せるので、
「記事を追加して」「コンテンツデータを確認して」といった指示をそのまま実行に落としやすくなります。

今回のゴールは次の 2 点でした。

  • MT 9 上で動く Perl プラグイン として実装する
  • Cursor / Claude などの MCP クライアントから HTTP 経由 で接続できるようにする

mt-plugin-mcp とは

mt-plugin-mcp は、MT の Data API エンドポイント上で MCP の JSON-RPC 2.0 を処理するプラグインです。
MT 管理画面へのログインセッションは不要で、管理画面から発行したアクセストークンで認証します。
トークンの有効期限は 7 日間 です。

全体構成

MCP クライアントからのリクエストは mt-data-api.cgi/v4/mcp に入り、MTMCP::App でトークンを検証したあと MTMCP::ProtocolMTMCP::Tools::* 経由で MT のオブジェクトを操作します。

リクエストの流れ

認証・認可

認証(Authentication) — クライアントは Authorization: Bearer <token> または X-MT-Authorization: MTAuth accessToken=<token> を付けて GET(SSE)/ POST(JSON-RPC)します。MTMCP::App::_check_authMT::Session->load(token)mt_session を参照し、kind == 'DA' かつ start + duration が期限内かを確認します。MT 管理画面へのログインセッションは不要です。

認可(Authorization) — トークンの発行システム > プラグイン > MT MCP Server > 設定 から行い、スーパーユーザーのみが操作できます(MTMCP::CMS::Token)。mt_session 行に author_id は保存していません。利用側は現状、有効なトークンがあれば MCP ツール一式を呼び出せます。発行画面の見た目は セットアップ手順 のスクリーンショットを参照してください。

データモデル

Tools 層が CRUD する MT オブジェクトの関係です。BLOG を中心に、用途別に分けています。

記事・カテゴリ

コンテンツタイプ

アセット・テンプレート

config.yaml で GET(SSE)と POST(JSON-RPC)の 2 エンドポイントを登録しています。
Claude Desktop など SSE トランスポートを使うクライアント向けに、GET で POST 先 URL を返す handle_sse も用意しています。

提供している主なツール

README に一覧がありますが、用途別に整理すると次のとおりです。

カテゴリ ツール例
ブログ・記事 blog_list, entry_list, entry_create, entry_update
カテゴリ・タグ category_list, tag_list
アセット asset_list, asset_get
テンプレート template_list, template_get, template_update
コンテンツタイプ content_type_list, content_type_get
コンテンツデータ content_data_list, content_data_create, content_data_update

README の 対応ツール一覧 と同等です。

エージェント向けに、ツール説明文へ「先に blog_list を呼ぶこと」といったガイドも入れています。
blog_idcontent_type_id を推測で埋めて失敗するケースを減らすためです。

セットアップ手順

README のセットアップ に沿って、おおまかな流れは次の 4 ステップです。

1. プラグインをインストール

cp -r plugins/MTMCP /path/to/mt/plugins/

MT を再起動(または touch mt.cgi)して、管理画面でプラグインを有効化します。

2. Apache の設定

Cursor から使う場合は Authorization: Bearer が前提 です。
curl では X-MT-Authorization でも試せますが、Cursor ではカスタムヘッダーがうまく渡らないケースがあり、Bearer 方式で運用しています。

そのため Apache 側で Authorization ヘッダーを CGI に渡す設定が必要になります。
サーバ設定を変更できるなら CGIPassAuth On を使います。

<Directory "/var/www/html/mt">
    CGIPassAuth On
</Directory>

.htaccess しか触れない環境では RewriteRule で代用できます。

RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

この設定がないと、Cursor から 401 Unauthorized になることがあります。
Apache を触れない環境だけ、curl 疎通確認用に X-MT-Authorization を使う選択肢があります。

3. アクセストークンを発行

MT 管理画面で システム > プラグイン > MT MCP Server > 設定 を開きます。

MT MCP Server の設定画面 — トークンを発行する

  1. トークンを発行する をクリック
  2. 表示されたトークンを コピー(画面下部に Cursor 用の mcp.json 設定例も表示されます)
  3. MCP クライアントの設定に貼り付け

トークンは 発行から 7 日間 有効です。
再発行しても、以前のトークンは即時無効にはなりません。期限切れのときは同じ手順で再発行してください。

4. MCP クライアントを設定

Cursor では ~/.cursor/mcp.jsonSSE トランスポート で登録します。
ヘッダーは Authorization: Bearer を使います(X-MT-Authorization は Cursor 側で期待どおり動かなかったため)。

{
  "mcpServers": {
    "movable-type": {
      "type": "sse",
      "url": "https://example.com/mt/mt-data-api.cgi/v4/mcp",
      "headers": {
        "Authorization": "Bearer <発行したトークン>"
      }
    }
  }
}

Cursor の mcp.json に movable-type を登録した例

Claude Desktop も同様に type: "sse" です。設定ファイルは claude_desktop_config.json になります。

ローカルの Docker 環境なら、URL は http://localhost:10000/cgi-bin/mt/mt-data-api.cgi/v4/mcp のような形になります。

認証ヘッダーの選び方

エンドポイントは GET(SSE)と POST(JSON-RPC)の 2 つです。

メソッド パス 役割
GET /mt-data-api.cgi/v4/mcp SSE 接続(POST 先 URL を受け取る)
POST /mt-data-api.cgi/v4/mcp JSON-RPC の送受信

認証ヘッダーは次の 2 通りです。用途によって使い分け ます。

ヘッダー Apache 設定 向いている用途
Authorization Bearer <token> CGIPassAuth On または RewriteRule Cursor / Claude Desktop
X-MT-Authorization MTAuth accessToken=<token> 不要 curl での疎通確認

curl で手早く試すなら X-MT-Authorization が楽です。
Apache の追加設定なしで POST できるため、プラグイン単体の動作確認に向いています。

一方 Cursor では headers にカスタムヘッダーを書いても、サーバまで届かないことがありました。
そのため MCP クライアントの設定は Authorization: Bearer に統一し、Apache 側で CGIPassAuth On または RewriteRule を入れています。

疎通確認

initialize(接続確認)

curl -X POST https://example.com/mt/mt-data-api.cgi/v4/mcp \
  -H 'Content-Type: application/json' \
  -H 'X-MT-Authorization: MTAuth accessToken=<your-token>' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"curl","version":"0.0.1"}}}'

成功すると次のようなレスポンスが返ります。

{"id":1,"jsonrpc":"2.0","result":{"capabilities":{"tools":{"listChanged":false}},"protocolVersion":"2024-11-05","serverInfo":{"name":"MT MCP Server","version":"0.1.0"}}}

tools/list(ツール一覧)

curl -X POST https://example.com/mt/mt-data-api.cgi/v4/mcp \
  -H 'Content-Type: application/json' \
  -H 'X-MT-Authorization: MTAuth accessToken=<your-token>' \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'

entry_create(記事作成)

curl -X POST https://example.com/mt/mt-data-api.cgi/v4/mcp \
  -H 'Content-Type: application/json' \
  -H 'X-MT-Authorization: MTAuth accessToken=<your-token>' \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"entry_create","arguments":{"blog_id":1,"title":"テスト記事","body":"本文です。","status":"draft"}}}'

Cursor から使うとこう動く

MCP サーバーが接続できていれば、チャットから自然言語で指示するだけです。
実際に Cursor からコンテンツデータを追加し、MT 管理画面に反映された例を載せます。

指示内容

http://example.com/cgi-bin/mt/mt.cgi
コンテンツタイプ、花にヒューケラ:シャンハイを追加して

管理画面の URL を添えて、どのサイトのどのコンテンツタイプかをエージェントに伝えています。

Cursor から「花」コンテンツタイプへヒューケラ:シャンハイを追加した様子

エージェントは content_data_create でデータを登録し、content_data_get で結果を確認しました。

レスポンス

「花」コンテンツタイプ(ID: 4)に「ヒューケラ:シャンハイ」を追加しました。

項目 内容
コンテンツデータ ID 7
ステータス 公開(publish)
花名 ヒューケラ:シャンハイ
特徴 銀色に紫の筋が入った葉が特徴的な常緑の宿根草。小さな白い花を初夏に咲かせます。
育て方 半日陰〜日陰の排水のよい場所に植えます。夏の高温多湿を避け、こまめに水やりを行います。

花名以外の「特徴」「育て方」も、エージェントがフィールド定義に合わせて補完しています。

MT 管理画面での確認

一覧にも追加されていました。

MT 管理画面 —「花」コンテンツデータ一覧にヒューケラ:シャンハイが表示されている

編集画面を開くと、各フィールドに値が入っていることも確認できます。

MT 管理画面 — ヒューケラ:シャンハイの編集画面

コンテンツデータの場合は、事前に content_type_get でフィールド ID を確認してから content_data_create を呼ぶ流れになります。花名だけ指定しても、エージェントが他のフィールドを埋めてくれるケースがあります。

トークンの期限が切れると 401 Unauthorized になるので、そのときは管理画面から再発行して mcp.json を更新してください。

実際にぶつかったハードケース

動かしてみると、MCP の仕様以前に 文字エンコーディングと DB charset でつまずく場面がありました。

日本語 JSON で Parse error

タイトルや本文に日本語を含む entry_create が Parse error になることがあります。
クライアント側で JSON を Unicode エスケープ形式(Python なら ensure_ascii=True)にして送ると通るケースがありました。

API レスポンスの文字化け

entry_list のレスポンス上ではタイトルが文字化けして見えることがあります。
MySQL から直接確認すると、DB 保存内容は正しい、という結果もありました。
保存失敗の判断は API レスポンスだけに頼らず、管理画面か DB で確認したほうがよいです。

絵文字で DB 保存エラー

本文に 4 バイト UTF-8 文字(絵文字など)が入ると、MySQL の utf8(3 バイト)環境では保存に失敗します。
utf8mb4 への移行が根本対応ですが、投入前に置換する回避も取れます。

長文失敗がサイズ問題に見える

本文を短くすると成功し、長くすると失敗する場合、文字数上限ではなく 特定の文字 が原因のこともあります。
500 / 1000 / 2000 文字で切って試し、二分探索で境界を特定すると原因を絞り込めます。

プラグイン内部の設計メモ

認証(7 日トークン)

初期版はプラグイン設定に固定文字列を置く方式でした。
現在は管理画面の トークンを発行する から MT::Session(kind: DA)を生成し、duration: 604800(7 日)で有効期限を管理しています。

_check_auth では次の順でトークンを拾います。

  1. Authorization: Bearer <token>
  2. X-MT-Authorization: MTAuth accessToken=<token>

セッションの start + duration を過ぎていれば Token expired を返します。
MT ログインセッションそのものは不要で、発行したトークンだけで MCP を呼べます。

詳細なフローは docs/architecture-auth.md にまとめています。

SSE と CORS

Cursor / Claude Desktop 向けに GET エンドポイントで SSE を返し、POST 先 URL を通知します。
OPTIONS ハンドラと CORS ヘッダーも入れており、ブラウザ経由のクライアントにも配慮しています。

JSON エンコーダ

ツール説明文に日本語を含めるため、レスポンス生成では JSON->new->ascii を使っています。
Perl の UTF-8 フラグと JSON エンコードの組み合わせで文字化けが起きやすいため、ASCII エスケープで統一しています。

Tools 層

MTMCP::Tools::EntryMTMCP::Tools::ContentData が MT のオブジェクト API をラップします。
MCP 側は「ツール名 + 引数 JSON」だけを受け取り、MT 内部の MT::Entry / MT::ContentData に変換する構成です。

トラブルシューティング

README の トラブルシューティング も参照してください。

症状 原因 対処
401 Unauthorized トークン無効・期限切れ 管理画面でトークンを再発行
SSE error: Non-200 status code (404) GET エンドポイント未登録 プラグインを最新版に更新
ツール説明が文字化け JSON エンコーダの不備(旧版) プラグインを最新版に更新
Unknown endpoint プラグインキャッシュが古い プラグインを無効→有効に切り替え
ログイン画面が返る エンドポイント URL の誤り mt-data-api.cgi/v4/mcp を使う

今後の展望

現状は CRUD 中心のツールセットです。
運用で欲しくなりそうなのは次のような拡張です。

  • コンテンツタイプ更新・削除
  • 公開日時やカテゴリの一括指定
  • リビルドトリガー連携
  • utf8mb4 前提の文字コード周りの整理

MT の管理画面を開かずに、エージェント経由で下書き投入まで持っていける状態にはなりました。
次は「公開フローまで任せられるか」「コンテンツ設計ごと MCP 化できるか」を試していく予定です。

まとめ

mt-plugin-mcp は、MT 9 に MCP サーバーを載せる Perl プラグインです。
管理画面から 7 日有効のトークンを発行し、Cursor から記事やコンテンツデータを操作できます。

セットアップの要点は次の 3 点です。

  • プラグインを plugins/MTMCP に配置して有効化する
  • 管理画面でトークンを発行し、Cursor の mcp.jsontype: "sse", Authorization: Bearer)に設定する
  • Apache で Authorization ヘッダーを CGI に渡す(CGIPassAuth On または RewriteRule)

ハマりどころは MCP プロトコルより、認証ヘッダーの渡し方と DB charset といった周辺部分でした。
Cursor では Bearer + Apache 設定がセット、curl だけなら X-MT-Authorization でも試せます。

リポジトリは公開しています。使い方の詳細は README を参照してください。

https://github.com/redamoon/mt-plugin-mcp

Discussion