MCPカスタムサーバーを自作してブログを自動公開した話

なぜ始めたのか

最近はAIエンジニアリングの準備に没頭していて、フルスタックAIプロジェクトをいくつかデプロイしたり、オープンウェイトのLLMをファインチューニングしたり、就職活動のためのDSAをこなしたりしていました。その真っただ中で、MCP(Model Context Protocol)に興味を持ち、ただ読むだけでなく実際に何かを作ってみようと決めました。

アイデアはシンプルでした。Claudeが私のマシン上にあるブログの下書きを読んで、きれいに整えて、Dev.toのUIを一切触らずにそのまま公開できたらどうだろうか?

それこそがMCPの目的だったのです。

MCPとは(簡単に)

MCPは、ClaudeのようなAIモデルが定義したツールを呼び出せるようにするプロトコルです。ファイルを読み込んだり、APIを叩いたり、スクリプトを実行したりといったことが可能になり、ただテキストを生成するだけではなくなります。小さなサーバーを作成して「ツール」(docstring付きのプレーンなPython関数)を公開し、それをClaude Desktopの設定に組み込むことで、Claudeは「話す」だけでなく「行動」できるようになります。

今回は2つのサーバーを用意しました:

  1. filesystem — 公式MCPサーバーで、指定したフォルダ内のファイルの読み書きをClaudeに許可する
  2. devto — 自作のサーバーで、publish_blog_to_devtoというツールを公開し、Dev.to APIをラップする

環境のセットアップ

Pythonのパッケージ管理にはuvを使い始めました。普通のpip/venvよりずっと高速です。

最初のトラブル: インストール直後にPowerShellからuvが見つかりませんでした。インストーラーがPATHを更新する前にターミナルセッションが既に開かれていたためです。ターミナルを閉じて再度開くと即座に解決しました。小さなことですが、原因が分からないと慌てやすいポイントです。

2つ目のトラブルはより興味深いものでした。新規でvenvを作成してuv add "mcp[cli]"を試みましたが、依存関係の解決に失敗し続けました。原因はpyproject.tomlrequires-python = ">=3.9"が残っていたことです。これはプロジェクトが当初システムPython 3.9向けに作成された名残で、実際のMCP SDKは3.10以上を必要としていました。以下のように明示的にピン留めして修正しました:

uv python pin 3.12
uv venv --python 3.12

Enter fullscreen mode Exit fullscreen mode

その後、pyproject.tomlrequires-python>=3.10に変更したところ、インストールは正常に完了しました。

パッケージ名トラップ

ここでしばらくハマった問題がありました。インストールが成功したはずなのに、fastmcpをインポートしようとするとModuleNotFoundError: No module named 'mcp.server.fastmcp'が発生しました。import mcp自体は問題なく、フォルダ内には確かにserverディレクトリが存在していました。

uv pip show mcpで確認したところ、インストールされていたパッケージはバージョン2.0.0で、httpx2mcp-typespyjwtpywin32といった依存関係がありました。これらは本物のMCP SDKのものではありませんでした。PyPI上でmcpという名前を別の無関係なパッケージが占有しており、本来のmodelcontextprotocol SDKの代わりにインストールされてしまっていたのです。

解決策はバージョン範囲を明示的に指定することでした:

uv remove mcp
uv add "mcp[cli]>=1.2.0,<2.0.0"

Enter fullscreen mode Exit fullscreen mode

これにより正規のSDK(当時1.29.0)がインストールされ、from mcp.server.fastmcp import FastMCPがようやく動作するようになりました。

教訓: uv add somepackageが「成功」したように見えてもその後がうまくいかない場合は、uv pip showで確認してください。PyPI上の名前が自分が思っているプロジェクトとは限らないからです。

Claude Desktopへの組み込み

Claude DesktopはMCPサーバーの一覧をclaude_desktop_config.jsonのトップレベルキーmcpServersの下から読み込みます。このファイルには他にも無関係なアプリケーション設定が含まれているため、サーバーをmcpServersの兄弟要素として追加してしまい、ネストの内側に入れ忘れるミスが起きやすいです。その場合、何も起こらずに静かに失敗します。私は設定画面の「Developer → Local MCP servers」でfilesystemしか表示されないのを眺めながら、このミスを痛感しました。

正しい形式:

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "C:\\Technical\\mcp-code"]
    },
    "devto": {
      "command": "C:\\Users\\vdine\\.local\\bin\\uv.exe",
      "args": ["--directory", "C:\\Technical\\custom-mcp\\devto-mcp-server", "run", "dev-server.py"]
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

Windows特有の注意点として、Claude DesktopはシェルのPATHを必ずしも継承しないため、"command": "uv"だけでは起動に失敗することがあります。where.exe uvで取得したフルパスを使うことで確実に起動できるようになりました。

Claude Desktopを完全に終了して再起動(ウィンドウを閉じるだけではトレイに残るため)したところ、両方のサーバーがrunningと表示されました。

MCP Inspectorでのテスト

Claude Desktopに組み込む前に、以下のコマンドでサーバーを単独でテストしました:

uv run mcp dev src/mcp_server_demo/__init__.py

Enter fullscreen mode Exit fullscreen mode

これによりMCP Inspectorが起動します。これはローカルのWeb UIで、ツールを直接呼び出して生のリクエスト/レスポンスペイロードを確認できるため、Claudeを介さずに早期にバグを発見できます。チャットインターフェース経由でデバッグするよりはるかに有用です。

ブログの公開

両方のサーバーが接続された状態で、実際の公開手順はほとんど拍子抜けするほどシンプルです。通常通りClaudeに話しかけるだけです:

「[フォルダ]にblog.txtというファイルがあるので、内容を洗練して、関連タグを付けてDev.toに下書きとして公開して」

Claudeは自らツールを連鎖させます:

  1. read_text_file(filesystemサーバー)を呼び出して生の下書きを取得
  2. 内容をインラインで書き直し・整形
  3. タイトル、マークダウン本文、タグを指定してpublish_blog_to_devto(自作サーバー)を呼び出し

Dev.toのエディタへのコピー&ペーストや手動でのフォーマットは一切不要です。まずpublished: falseを設定して下書きとして確認してから公開するようにしています。未完成のものを公開してしまうリスクを抑えるための安価な保険です。

これで実際に学んだこと

ここでの本当の学びはMCPプロトコルそのものではなく、標準的な環境デバッグでした。PATHの問題、Pythonバージョンのピン留め、名前を乗っ取られたパッケージ、JSONのネストミスなどです。MCP自体は、環境さえ整っていればdocstring付きの20行程度のPythonで済みました。

おそらく見過ごされがちな教訓は、エージェントツールは下層の退屈な配管がどれだけ信頼できるかにかかっているということです。venv、パッケージバージョン、設定の形式を正しく整えれば、「AIが面白い部分をやってくれる」は自然に実現します。

次は同じサーバーにさらにツールを追加する予定です。GitHubのコミット履歴を取得して「今週作ったもの」投稿の下書きを自動生成するツールなどを検討しています。