弊社環境におけるRedmineのMCPサーバー構築について

2026/08/31

はじめに

弊社では、社内のプロジェクト管理にRedmineを利用しています。
今回、ClaudeからRedmineのチケットを直接参照・操作できるようにするため、MCP(Model Context Protocol)サーバーを構築しました。
本記事では、その選定から構築、トラブルシューティングまでの一連の流れを記事にしようと思います。

同じような構成を検討されている方の参考になれば幸いです。

背景・目的

弊社では、普段開発で利用しているClaude Codeでは、APIを使えばRedmineの操作ができていたのですが、Claudeのチャットから操作しようとしたところ、Claudeの制限のためできないとClaudeがいうのでどうしたらできるか確認したところ、MCPサーバーを構築したらできるよってことなので、構築することになりました。

MCPサーバーの選定

RedmineのMCP対応には、大きく分けて2つのアプローチがあります。

  1. Redmineプラグインとして実装する方式:Redmine本体にプラグインを追加し、Redmineプロセス自体がMCPサーバーとして振る舞う
  2. 外部接続型:Redmine本体は一切変更せず、既存のREST APIとAPIキーを使って外部のMCPサーバーからアクセスする方式

はじめはRedmineプラグイン方式(redmine_mcp)も検討しましたが、

  • リリースから日が浅く実績(Star数・評価件数)がほとんどない
  • Redmine本体のメジャーバージョンアップ(v5→v7)が前提になる
  • 認証方式についてドキュメント上の記載が薄い

といった理由から、Redmine本体のバージョンを変更せずに済む「外部接続型」を採用することにしました

外部接続型の中でも、いくつかのOSS実装を比較検討しました。

runekaagaard/mcp-redmine
jztan/redmine-mcp-server
yonaka15/mcp-server-redmine

などですね。

観点網羅型実装機能特化型実装
APIカバレッジRedmine APIをほぼ100%カバー主要なツールに絞って実装
読み取り専用モードサーバー側での強制はできない環境変数で書き込み系ツールを丸ごと無効化可能
プロンプトインジェクション対策ソースコードレベルでは実装あり(ドキュメント記載なし)ドキュメントにも明記されている
デプロイ形態ローカルプロセス(stdio)前提HTTP常設サーバー前提。ビルド済みDockerイメージも公開

上記のような観点に加え、jztan/redmine-mcp-server 以外は、ローカルで構築する前提だったりの資料とかだったりしたので、今回目指している、「社内サーバーとして常設稼働させ、複数人・Claudeのコネクタ機能から利用する」という運用方針に合致していた機能特化型・HTTPサーバー前提のjztan/redmine-mcp-serverを採用しました。読み取り専用モードを切り替えられる点、プロンプトインジェクション対策がドキュメントにも明記されている点も決め手になりました。

構築の全体像

最終的な構成は以下の3層です。

Redmine(既存の社内サーバー・バージョン変更なし)
   ⇕ REST API(APIキー認証)
MCPサーバー(新規構築・Dockerコンテナ)
   ⇕ HTTPS(Basic認証)
Nginx(リバースプロキシ)
   ⇕
Claude(組織のカスタムコネクタとして登録)

MCPサーバーはDockerで構築し、既存のRedmineサーバーとは別のコンテナとして切り出しました。理由は以下の通りです。

  • 非rootユーザーでの実行がイメージに組み込まれておりセキュリティ的に望ましい
  • ヘルスチェック機構が標準装備されている
  • Redmine本体の依存関係・アップデートサイクルに影響を与えない

構築手順

1. Redmine側の準備

Redmine管理画面でAPIアクセスを有効化し、APIキーを発行しました。可能な範囲で、必要な操作のみに絞った権限を持つ専用ユーザーを作成し、そのユーザーのAPIキーを利用する形にしています。

2. Docker Composeでの起動

.envファイルに接続情報を記述し、docker compose up -dで起動する構成にしました。

公式でも、

cat > .env << 'EOF'
REDMINE_URL=https://your-redmine-instance.example.com
REDMINE_API_KEY=your-api-key-here
REDMINE_MCP_READ_ONLY=true
SERVER_HOST=0.0.0.0
SERVER_PORT=8000
EOF

cat > docker-compose.yml << 'EOF'
services:
  redmine-mcp:
    image: ghcr.io/jztan/redmine-mcp-server:latest
    env_file: .env
    ports:
      - "8000:8000"
    restart: unless-stopped
EOF
docker compose up -d

とあるだけなので、割と簡単にサーバーを構築することができました。

3. Nginxによるリバースプロキシ・HTTPS化

ClaudeのカスタムコネクタはAnthropicのクラウドインフラから接続される仕様のため、MCPサーバーは外部から到達可能なHTTPSエンドポイントである必要があります。Let’s Encryptで証明書を取得し、Nginxでリバースプロキシを構成しました。

4. アクセス制限(Basic認証)の追加

URLさえ分かれば誰でもアクセスできてしまう状態を避けるため、Nginx側にBasic認証を設定しました。ClaudeのカスタムHTTPヘッダー機能(static_headers、ベータ機能)を使い、組織の管理者がコネクタ設定時に一度だけ認証情報を登録することで、以降はClaudeが自動的に認証ヘッダーを付与してくれる構成です。
IPアドレスの制限にしようかなと思ったのですが、いったんBasic認証で接続確認だけでも行いたくそのまま続けました。またIPアドレスで制限はかけると思います。

トラブルシューティングの記録

構築中に発生した主な問題と原因を記録として残します。同様の構成を組む方の参考になれば幸いです。

問題1: ポートのミスマッピングによる接続リセット

症状curlでヘルスチェック用エンドポイントにアクセスすると、接続がリセットされる。コンテナ自体のステータスはhealthy

原因:Dockerのポートマッピング設定(ホスト側ポート:コンテナ内部ポート)と、アプリケーション自体がコンテナ「内部」でリッスンしているポート番号の設定に食い違いがありました。環境変数でアプリ内部のリッスンポートを誤って変更してしまい、Dockerが転送しようとした先に誰もいない状態になっていたのが原因でした。

教訓:コンテナの「healthy」ステータスは、あくまでヘルスチェックの設定自体が参照している値との整合性を見ているだけであり、外部からの到達性を保証するものではない、という点に注意が必要です。

問題2: リバースプロキシ経由での502エラー

症状:Nginx経由でアクセスすると502 Bad Gatewayが返る。

原因:問題1と同根で、Nginxのproxy_pass先(コンテナのホスト公開ポート)への転送自体が、コンテナ内部でリッスンしていないポートに向いていたために失敗していました。recv() failed (Connection reset by peer)というエラーログから、TCP接続自体は確立できるが、応答を受け取る段階で相手から切断されていることが特定の手がかりになりました。

問題3: Basic認証のパスワード比較失敗

症状:Basic認証のパスワードに記号を含めていた際、Nginxのエラーログに認証失敗のログが出力される。

原因:シェルコマンドでBase64エンコードする際、パスワードに含まれる記号の扱いに起因する問題でした。

問題4: Authorizationヘッダーのスペース欠落

症状:パスワードを記号なしのものに変更した後もBasic認証が通らない。今度はエラーログに具体的な失敗理由が出力されず、アクセスログ上のステータスコードのみ401が記録される状態に。Claude Desktop側では「サーバーに接続できませんでした」という汎用的なエラーメッセージのみが表示されていました。

原因:ヘッダーの値をNginx側でログ出力して確認したところ、Basicという文字列とBase64エンコードされた認証情報の間にあるべき半角スペースが欠落していることが判明しました。HTTP Basic認証の仕様上、BasicとBase64値の間には半角スペース1つが必須です。Claudeのヘッダー入力欄への入力方法(「Basic」と入力→スペースキー→Base64値をペースト、という分割入力)が原因で、スペースが失われていたと考えられます。

対処Basic から始まる文字列全体をコマンドで一括生成し、一度のペースト操作でヘッダー値欄に入力することで解消しました。

教訓:認証エラーの切り分けにおいて、「サーバーに送られてきている値そのもの」を一時的にログへ出力して確認する手順が、原因特定への一番の近道でした。ヘッダーの内容を一時的にログ出力する設定は、検証後は忘れずに元に戻す必要があります。

まとめ

Redmine本体のバージョンアップを伴わずに、外部接続型のMCPサーバーをDockerコンテナとして別立てすることで、ClaudeからRedmineのチケットを自然言語で参照・操作できる環境を構築できました。

構築時のポイントを改めて整理すると、以下の通りです。

  • MCPサーバーの選定では、機能の網羅性だけでなく「読み取り専用モードの有無」「プロンプトインジェクション対策の実装状況」といったセキュリティ面も比較軸に含めるべき
  • Dockerのポートマッピングは「ホスト側ポート」と「コンテナ内部のリッスンポート」が食い違うと分かりにくい障害につながるため、環境変数の意味を正確に理解した上で設定する
  • 外部公開するMCPサーバーには、Basic認証などの追加のアクセス制御を必ず設けるべき
  • 認証まわりのトラブルは、実際に送信されているヘッダーの中身を一時的にログへ出して確認するのが最も確実な切り分け方法

おわりに

ここまで読んでいただき、ありがとうございました。
私たちは、できることを誠実にやる会社です。
これからどうぞよろしくお願いします。
Web・システム・AIのことで気になることがあれば、
「何から相談すればいいか分からない」段階でも大丈夫です。
お気軽にご相談ください。

▶ お問い合わせ

Claudeでのやり取りから本記事を作成し、加筆・修正しております。

サニージェム公式note

アーカイブ記事

すべて

contact us お問い合わせ

Contact お問い合わせ・ご相談

「何から相談すればいいかわからない」
そんな段階でも、お気軽にご連絡ください。

Recruit 求人へのご応募

サニージェムの考え方に共感してくれる方と、
ぜひ一緒に働きたいと思っています。