はてなブログ用のMCPサーバーを作った。更新にも対応

最近、はてなブログ用のMCPサーバーを作った。hatena-blog-mcp という名前で、更新系のAPIまで対応している。リポジトリは Keisuke69/hatena-blog-mcp に置いてある。経緯と使い方を軽くまとめておく。

github.com

なぜ作ったか

このブログ、何気に200記事を超えているのだがカテゴリの付け方が長年わりと雑だったので一度まとめて整理し直したくなった。さらにその先として、1記事に複数トピックが混ざっている記事が86本ほどあって、いずれ分解したいと思っている。分解の前段として、まずはカテゴリをちゃんとしたい、というのがきっかけ。

200記事超のカテゴリを手作業で付け替えるのは、まあ現実的じゃない。この手の単調な作業は自動化したい。そして昨今、自動化といったら生成AIだ。つまりClaudeからできるようにしたい。

というわけで、まずは「はてなブログ用のMCPサーバー、もう誰か作ってるよね?ワンチャン公式あったりして」と探すところから始めた。

公式はなかった。

そして有志の方が作ったものはいくつか見つかった。ただ、どれも現時点では読み取り専用っぽい。記事を取得したり検索したりはできるけど、更新系には対応していない。それだと今回の用途には足りない。

「じゃあ AtomPub API を直接叩くスクリプトを書くか」と一瞬思ったんだけど、最近の僕はそもそも手元に実行環境をローカルに用意しないといけないこと自体が面倒になっている。なのでどこかにデプロイしてサクッと使いたい。そうなると地味に手間がかかる。だるい。

で、気づいたらMCPサーバーを作っていた。

スクリプト書くよりMCPサーバーのほうが作る手間があるに決まっているんだけど、一度作ってしまえばClaudeから直接叩けるし、今後のブログ運用でもずっと使える。単発スクリプトより汎用性があるだろうと自分に言い聞かせた。

ちなみに実装はもちろんClaude Codeに手伝ってもらっている。ていうかもはや最近だといちいち言う必要もないよねってくらい使われてるはず。

はてなブログの AtomPub API

そういえばMCPサーバーを探すところから始めてしまったので、はてなブログの AtomPub API 自体はそんなにちゃんと見ていなかった。改めて確認すると、記事のCRUDもカテゴリ取得も一通りできる。固定ページ(/about とか /privacy-policy とか)の操作も可能。認証は Basic認証で、APIキーは管理画面から取れる。

つまり、読み取り専用のMCPサーバーしか見つからなかったのは、既存APIの制約というより、単に更新系の対応をまだ誰も作っていなかっただけ、っぽい。

作ったもの

ツールは全部で11個。大きく以下の3カテゴリ。

  • エントリのCRUD: list_entries, get_entry, create_entry, update_entry, delete_entry
  • 固定ページのCRUD: list_pages, get_page, create_page, update_page, delete_page
  • カテゴリ一覧: list_categories

サーバー側で認証情報は保持しない。クライアントが毎回リクエストのヘッダに Basic認証を乗せる形にした。一人で使ってもいいし、複数人で共有して、それぞれが自分のAPIキーを使うこともできる。

動かす場所はわりと自由だが、僕は前回のmcp-oauth-proxyに引き続きCloudflare Workers に置いている。HTTPサーバーが立てばだいたい動くはずだがそこはあんまり確認していない。あくまでお手軽にデプロイしたかったので Workers にしただけ。

設計で気をつけたのは一つだけで、「触ってないものは触らない」ということ。

カテゴリだけ変えたつもりなのに、タイトルや本文や投稿日時まで上書きされたら普通に事故る。だから update_entry は、渡したフィールドだけしか送らない仕様にした。省略したフィールドは既存エントリの値をそのまま維持する形。

もう少し細かい話をするならば、本文の記法(content_type)も常に既存エントリから取ってくるようにしている。ここを勝手に切り替えられると、Markdown記事がプレーンテキストに化けるようなことが起こり得て思わぬ事故になりかねない。投稿日時(updated)も、明示的に touch_updated: true を指定しない限り既存の値を維持するし、URLスラッグ(custom_url)も明示指定しない限り変えない。タイムラインの並びとかパーマリンクを無意識に壊れるのは怖すぎる。

この辺りはClaudeに記事更新を任せる前提で設計している。Claudeは便利なんだけど、お節介な部分も多々あるので何でも書き換えられると怖い、というのが正直なところ。

使い方

デプロイ(Cloudflare Workers の場合)

pnpm install
pnpm exec wrangler login
pnpm exec wrangler deploy

これだけ。シークレットも KV も Durable Objects も要らない。完了すると URL が https://hatena-blog-mcp.<あなたのサブドメイン>.workers.dev のような形で出てくる。別の環境で動かしたい場合は適宜読み替えてもらえれば。

認証情報を用意

  • はてなID: ブログURLの左側(<hatena_id>.hatenablog.com
  • APIキー: はてなブログの設定 → 詳細設定 → AtomPub から取得

クライアントに設定

Claude Desktop / claude.ai(Web・モバイル)から使う場合はカスタムコネクタとして設定するが、例の通りClaudeのカスタムコネクタはOAuth形式しか対応していないので僕は前回作ったmcp-oauth-proxyを挟んでいる。その上で設定として上流のURLとして今回作ったはてなブログMCPサーバーを指定し、認証情報として必要なものを送る感じ。

  • URL: https://hatena-blog-mcp.<あなたのサブドメイン>.workers.dev/mcp
  • 認証: Basic認証、ユーザー名(はてなID):パスワード(AtomPub APIキー)、もしくはbase64エンコードしたもの

気をつけて欲しいのは認証のところの値としてBearerではなくBasicにすること。自分で作っておいてここでしばらくハマった。あと、トークンは平文で指定することも可能だがbase64エンコードしたものを渡すことをおすすめする。base64した値はprintf '%s' 'hatena_id:api_key' | base64 で作れる。

実際にやりたかったこと

繋いでしまえば、あとはClaudeに頼むだけでいい。例えばこんな感じで雑に頼む。

「ブログ example.hatenablog.com の全エントリを list_entries で列挙して、各エントリの本文を読んだうえでカテゴリを整理し直してください。タイトル・本文・投稿日時は変更しないでください。」

これで全記事のカテゴリ整理をClaudeに任せられる状態になった。当初やりたかった86記事の分解の前段として、まずカテゴリ整理が捗るはず。

一点注意があって、上の例では list_entries を叩いているがはてなのAPIの仕様上は7件でページングされることと、本文のコンテンツも全部取得されてそのままレスポンスされるのでコンテキストを消費しがち。なので、対象記事の entry_id を自分で特定して渡すとか別セッションで web_fetch でURL一覧取得して list_entries で各記事の entry_id 抽出して、それを使って処理させるとかのほうが良い。この辺りはそのうち本文などをレスポンスに含めないオプションなど用意しようかと思ってる。

ソースは Keisuke69/hatena-blog-mcp に置いてある。よろしければご自由にお使いください(当然ながら何の責任も取れませんが)。

github.com

変更や追加があったらまた書くかもしれない。

©Keisuke Nishitani, 2023   プライバシーポリシー