はじめに
前編では、Redmine と MCP を組み合わせる場合、利用者ごとではなく、
権限・責務・Trust Boundary ごとに MCP Server を分離する
という設計を整理した。
実装編では、この設計を Linux サーバー上へ実際に配置する方法を考える。
想定する最終構成は次のようなものだ。
ChatGPT / AI Agent
│
├─ Reader MCP
│ └─ Reader API Key
│
├─ Writer MCP
│ └─ Writer API Key
│
├─ Agent MCP
│ └─ Agent API Key
│
└─ Admin MCP
└─ Admin API Key
│
↓
Redmine
ただし、最初から4系統すべてを作る必要はない。
少人数運用では、
Reader
Writer
の2系統から始め、
AI Agent Runner を導入した段階で、
Agent
を追加する方が運用しやすい。
1. 実装上の基本原則
今回の構成では、認可を一箇所だけに依存させない。
次の複数層で制御する。
ChatGPT / Agent
↓
接続可能な MCP
↓
MCP Server の Tool / mode
↓
Redmine API Key
↓
Redmine Role
↓
Project Permission
このうち重要なのは、Redmine を最終的な認可境界として残すことだ。
MCP Server 側で操作を制限していても、Redmine API Key に不要な権限を与えない。
逆に Redmine 側が read-only でも、MCP 側で不要な write Tool を公開しない。
つまり、
MCP は操作面を制限し、Redmine は権限面を制限する。
2. MCP Server 専用 OS ユーザーを作る
Redmine と同じ Linux サーバーで MCP Tunnel Client を動かす場合でも、Redmine 本体と同じ OS ユーザーでは起動しない方がよい。
例えば、
redmine
redmine-mcp
を分離する。
MCP Server は Redmine REST API を経由して操作するため、通常は Redmine の Rails アプリケーションディレクトリや DB ファイルへ直接アクセスする必要はない。
理想的な境界は次の通りだ。
redmine-mcp user
├─ Tunnel Client 実行
├─ MCP Server 実行
├─ MCP用設定ファイル読取
└─ Redmine REST API 接続
アクセス不要
├─ Redmine DB
├─ Redmine secret
├─ Rails application files
└─ Redmine OS service
MCP Server が侵害された場合でも、Redmine 本体へ OS レベルで横移動しにくくなる。
3. ディレクトリを分離する
具体的なパスは環境依存なので固定する必要はないが、責務としては次のように分離するとよい。
MCP runtime
├─ executable
├─ runtime working directory
└─ logs / temporary files
MCP secrets
├─ control plane credential
├─ tunnel identifier
└─ Redmine API credential
systemd
└─ service definitions
例えば一例として、
/opt/redmine-mcp/
/etc/redmine-mcp/
/etc/systemd/system/
のように分けられる。
ここで重要なのはディレクトリ名ではなく、
実行物と秘密情報を分離すること
である。
4. API Key を権限ごとに分離する
最も重要なのが API Key の分離だ。
悪い例は、
Reader MCP ─┐
Writer MCP ─┼─ 同じ管理者API Key
Agent MCP ──┘
である。
これでは MCP Server を分けても、Redmine 側の権限境界が存在しない。
代わりに、
Reader MCP
└─ Reader用API Key
Writer MCP
└─ Writer用API Key
Agent MCP
└─ Agent用API Key
とする。
可能であれば、それぞれ Redmine 上でも別ユーザーとする。
mcp-reader
mcp-writer
mcp-agent
こうすると Redmine の journal や監査情報から、
どの権限主体が変更したか
を区別しやすくなる。
5. Reader Role を作る
Reader は最も単純である。
成立させる責務は、
ChatGPT が Redmine の情報を取得できるが、状態変更はできない
ことである。
Reader の Redmine Role には、運用上必要な閲覧権限だけを与える。
概念的には、
Reader Role
Allowed
├─ Project 閲覧
├─ Ticket 閲覧
├─ Journal 閲覧
├─ Version 閲覧
└─ Wiki 閲覧
Denied
├─ Ticket 作成
├─ Ticket 更新
├─ コメント追加
├─ Status変更
├─ Version変更
├─ Member変更
└─ Project設定変更
となる。
さらに MCP Server 自体も read-only mode で起動する。
MCP Layer
read-only
+
Redmine Layer
read-only role
この二重化が重要である。
6. Writer Role を作る
Writer は、人間が ChatGPT を通して通常のチケット操作を行うための権限である。
例えば、
Allowed
├─ Ticket 閲覧
├─ Ticket 作成
├─ Ticket 更新
├─ コメント追加
├─ Status変更
├─ 関連Ticket操作
└─ 必要なVersion参照
Denied
├─ Project削除
├─ Member管理
├─ Role管理
├─ System設定
└─ 不要な管理操作
のような境界を作る。
ここで重要なのは、
Writer = Administrator
にしないことだ。
ChatGPT がチケット作成・更新を行うだけなら、Redmine 全体を管理する権限は不要である。
7. Agent Role は Writer と分離する
AI Agent Runner を導入するときは、Writer の API Key を流用しない。
Agent 専用 Role を作る。
例えば Agent Runner の責務が、
Ready for Agent の Ticket を取得
↓
Agent Brief を取得
↓
実装開始を記録
↓
結果を Journal へ記録
↓
定義された状態へ遷移
であるなら、それ以外の権限は基本的に不要である。
概念的には、
Agent Role
Allowed
├─ 指定ProjectのTicket閲覧
├─ Journal追加
├─ Agent workflowで必要なTicket更新
└─ 許可された状態遷移
Denied
├─ 任意Ticket削除
├─ Project管理
├─ Member管理
├─ Version削除
├─ Role管理
└─ System設定
となる。
8. Workflow の状態遷移も権限として扱う
AI Agent の権限管理では、
「Ticket を編集できるか」
だけでは不十分である。
誰がどの状態へ遷移できるかも重要だ。
例えば Agent Brief Workflow が、
Brief Draft
↓ Human Review
Brief Ready
↓ Handler Validation
Ready for Agent
↓ Agent
Running
↓
Verification
なら、
Human Writer
Brief Draft → Brief Ready
Handler
Brief Ready → Ready for Agent
Agent Runner
Ready for Agent → Running
CI / Handler
Running → Verification / Result
のように責務を分離できる。
理想的には、Redmine の Workflow 設定と Role を利用して、
Agent Runner が Human Review を飛ばして Ready for Agent に変更できない
状態にする。
これは単なる UI 制御ではなく、AI Agent に対する security boundary になる。
9. systemd service を権限ごとに分ける
Linux 上では、MCP Server を権限別に別サービスとして管理する。
例えば、
redmine-mcp-reader.service
redmine-mcp-writer.service
redmine-mcp-agent.service
という構成にする。
Reader と Writer を同じプロセスへまとめて、リクエストごとに API Key を切り替える設計も技術的には可能だが、小規模な環境ではサービス自体を分離した方が理解しやすい。
理由は、
- credential の混在を防ぎやすい
- systemd 単位で停止できる
- ログを分離できる
- 障害範囲を限定できる
- 設定ミスを発見しやすい
ためである。
10. Reader service の構成例
以下は構造を示す例である。
実際の executable path、オプション名、環境変数名は、導入している Tunnel Client と MCP Server の現行仕様に合わせる必要がある。
[Unit]
Description=Redmine MCP Reader
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=redmine-mcp
Group=redmine-mcp
EnvironmentFile=/etc/redmine-mcp/reader.env
ExecStart=/path/to/tunnel-client run \
--control-plane.api-key env:CONTROL_PLANE_API_KEY \
--control-plane.tunnel-id ${CONTROL_PLANE_TUNNEL_ID} \
--mcp.command channel=main,command=<reader-mcp-command>
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Reader 用 environment file には Reader 用 credential だけを配置する。
CONTROL_PLANE_API_KEY=...
CONTROL_PLANE_TUNNEL_ID=...
REDMINE_URL=...
REDMINE_API_KEY=<reader credential>
REDMINE_MCP_READ_ONLY=true
11. Writer service の構成例
Writer も別 service とする。
[Unit]
Description=Redmine MCP Writer
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=redmine-mcp
Group=redmine-mcp
EnvironmentFile=/etc/redmine-mcp/writer.env
ExecStart=/path/to/tunnel-client run \
--control-plane.api-key env:CONTROL_PLANE_API_KEY \
--control-plane.tunnel-id ${CONTROL_PLANE_TUNNEL_ID} \
--mcp.command channel=main,command=<writer-mcp-command>
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Writer 側は、
REDMINE_API_KEY=<writer credential>
REDMINE_MCP_READ_ONLY=false
となる。
Reader と Writer で秘密情報ファイルを共用しない。
12. Agent service ではさらに境界を強くする
Agent Runner 用 MCP では、人間用 Writer より強い制限を考える。
例えば、
Agent MCP
├─ Agent専用 Redmine user
├─ Agent専用 API Key
├─ Agent専用 Role
├─ Agent対象 Project のみ参加
└─ Agent Workflowで必要な権限のみ
とする。
Agent が何らかの理由で誤った Tool を呼んでも、
MCP Tool
↓
Redmine Role
↓
Workflow
↓
Project membership
のどこかで拒否できる設計が望ましい。
13. EnvironmentFile の権限を絞る
API Key や Tunnel credential を unit ファイルへ直接書かない。
秘密情報は別ファイルへ配置する。
例えば、
/etc/redmine-mcp/reader.env
/etc/redmine-mcp/writer.env
/etc/redmine-mcp/agent.env
とする。
ファイル自体も、
root
または
専用 service account
以外から読めないようにする。
重要なのは、
一般ユーザー
↓
service definition は読める
秘密情報
↓
読めない
という分離である。
14. MCP Server のバージョンを固定する
常駐サービスでは、起動するたびに最新バージョンを取得する構成は避けた方がよい。
例えば、
npx package
だけでは、その時点の最新版へ更新される可能性がある。
常駐環境では、
特定version
へ固定するか、
事前にインストールした executable
を使う。
理由は、
server reboot
↓
MCP Server が暗黙アップデート
↓
Tool contract が変化
↓
ChatGPT側の挙動が変化
という状態を防ぐためである。
Infrastructure restart と Application upgrade は分離した方がよい。
15. Tunnel と MCP Server のアップグレードも分離する
同様に、
Tunnel Client
MCP Server
Redmine
を一度にアップグレードしない方がよい。
例えば、
1. Reader MCP で新バージョン確認
2. 接続確認
3. read operation確認
4. Writerへ展開
5. write operation確認
6. Agentへ展開
のように段階的に更新する。
Reader を最初の Canary として使える。
16. 起動確認
systemd へ登録した後は、
service enabled
service active
tunnel connected
MCP initialized
Redmine reachable
をそれぞれ確認する。
ここで、
プロセスが起動していること
と、
MCP が利用可能であること
は別条件として扱う。
例えば Tunnel Client のプロセスが active でも、
- Redmine API Key が無効
- MCP subprocess 起動失敗
- Tunnel 接続失敗
などはあり得る。
したがって service status だけで運用正常とは判断しない。
17. Reader の検証
Reader の検証では、
成功ケースだけでなく拒否ケースを確認する。
成功すべき操作
Project取得
Ticket取得
Roadmap取得
Journal取得
Wiki取得
失敗すべき操作
Ticket作成
Ticket本文変更
コメント追加
Status変更
Ticket削除
重要なのは、
read が成功することと write が失敗することの両方
を確認することである。
18. Writer の検証
Writer では、
Ticket取得
Ticket作成
Ticket更新
コメント追加
許可されたStatus変更
が成功することを確認する。
同時に、
Member管理
Role変更
Project削除
System設定変更
など、Writer の責務外操作が拒否されることも確認する。
19. Agent の検証
Agent では通常の CRUD だけでなく Workflow Contract を確認する。
例えば、
Ready for Agent
↓
Running
は成功する。
しかし、
Brief Draft
↓
Running
は拒否される。
また、
Agent対象Project
では操作できるが、
別Project
にはアクセスできない、あるいは変更できないことも確認する。
Agent の Acceptance Criteria は、
できること
だけでなく、
できてはいけないこと
を含める必要がある。
20. ログを service 単位で分離する
systemd を使う利点の一つが journal である。
例えば、
Reader service logs
Writer service logs
Agent service logs
がそれぞれ分離される。
障害時には、
誰向けのMCPだったか
どのcredentialだったか
どのserviceが失敗したか
を切り分けやすい。
複数の権限を1プロセスへまとめると、この境界が曖昧になりやすい。
21. Redmine の Journal も監査ログとして利用する
MCP 側のログだけでなく、Redmine 自身の Journal も重要である。
例えば API Key を、
mcp-reader
mcp-writer
mcp-agent
という別ユーザーへ割り当てていれば、
誰が変更したか
を Redmine 側でも区別できる。
AI Agent が更新した Ticket を人間が更新したように見せないことが重要だ。
監査上、
Human
Agent
Automation
は別 principal として扱う方がよい。
22. Admin MCP は常用しない
Admin MCP を作る場合でも、通常の ChatGPT 接続先として常時利用する必要はない。
管理操作には、
- Member変更
- Project設定
- Role変更
- Version管理
- 削除系操作
など、通常開発より影響の大きい操作が含まれる。
そのため、
Reader
Writer
Agent
は常駐、
Admin
は必要時のみ有効化する設計も考えられる。
例えば systemd 上で、
disabled by default
にしておき、必要な管理作業時だけ起動する。
これにより、高権限 credential の exposed time を短くできる。
23. ChatGPT 側の権限と混同しない
重要なのは、
ChatGPT から MCP が見えることと、その操作が許可されていることは別問題
という点である。
仮に ChatGPT が Writer MCP を呼び出せたとしても、
MCP server
↓
Redmine API
↓
Role / Workflow
で最終的な認可を行える。
逆に Redmine 側が強権限であれば、
「ChatGPT が適切に判断してくれる」
ことだけを security boundary としてはいけない。
AI の判断と認可は分離する。
24. MCP Tool Contract も必要最小限にする
Redmine Role が最終防御になるとしても、Agent 向け MCP へ不要な Tool を公開する必要はない。
例えば Agent Runner が、
get issue
add journal
update limited workflow state
しか必要としないなら、
Project削除や Member変更といった Tool はそもそも見せない方がよい。
理想的には、
Tool exposure
↓
API permission
↓
Workflow permission
の3段階で絞る。
25. 利用者が増えた場合の拡張
最初の2人程度なら、
Writer MCP
だけから始める運用も可能である。
その後、
Step 1
Writer
から、
Step 2
Reader
Writer
へ分離する。
AI Agent 導入後、
Step 3
Reader
Writer
Agent
とする。
必要になった場合のみ、
Step 4
Reader
Writer
Agent
Admin
へ拡張する。
人数が増えたからプロセスを増やすのではなく、
新しい Trust Boundary が生まれたときだけ増やす。
26. さらに大規模になった場合
利用者が数十人・数百人になった場合には、単純な共有 API Key 方式だけでは監査や個人単位の認可が不足する可能性がある。
その段階では、
User Identity
↓
MCP Gateway / Authorization Layer
↓
credential selection / delegated identity
↓
Redmine
のような追加認可レイヤーを検討することになる。
ただし、2〜10人程度のチームで最初からこの仕組みを作るのは過剰になりやすい。
まずは、
権限別MCP
+
権限別API Key
+
Redmine Role
で十分なケースが多い。
27. AI Agent Runner との接続
最終的に Agent Runner を導入すると、
Redmine
│
Ready for Agent Ticket
↓
Agent Handler
↓
Agent Runner
↓
Git
↓
CI
という流れになる。
Agent Handler が Redmine を操作するときには、人間用 Writer MCP を使わせず、
Agent MCP
を使わせる。
これにより、
Human permission boundary
と、
Machine permission boundary
を分離できる。
28. GitHub 側も同じ考え方で分離する
この考え方は Redmine だけで終わらない。
Agent Runner が GitHub へアクセスする場合も、
Redmine Agent Credential
と、
GitHub Agent Credential
を分離する。
Agent Runner に必要なのは一般的には、
対象repository取得
branch作成
commit / push
PR操作
CI結果取得
など、Agent Workflow に必要な範囲である。
Organization 管理権限まで持たせる必要はない。
つまり Agent Runner 全体では、
Agent
├─ Redmine: workflow最小権限
└─ GitHub: source最小権限
という構成になる。
29. Redmine と GitHub の責務境界
最終的な構成は次のようになる。
Human
│
ChatGPT
│
Writer MCP
│
Redmine
│
Ready for Agent
↓
Agent Runner
/ \
/ \
Agent MCP Git credential
│ │
Redmine GitHub
│
↓
CI
責務としては、
Redmine
├─ Roadmap
├─ Ticket
├─ Agent Brief
├─ Approval
├─ Workflow State
└─ Execution metadata
GitHub
├─ Source
├─ Branch
├─ Commit
├─ Pull Request
└─ CI
となる。
30. 実装時の完了条件
この権限管理基盤を導入するときは、単に service が起動しただけで完了にしない。
少なくとも次を確認する。
[ ] MCP runtime が専用OSユーザーで動作する
[ ] credential が service definition と分離されている
[ ] Reader と Writer が別credentialになっている
[ ] Reader の write 操作が Redmine 側でも拒否される
[ ] Writer が必要な Ticket 操作を実行できる
[ ] Writer が管理操作を実行できない
[ ] systemd restart 後に自動復旧する
[ ] OS reboot 後に自動起動する
[ ] service ごとにログを確認できる
[ ] Redmine Journal 上で操作主体を識別できる
Agent を導入する場合はさらに、
[ ] Agent専用credentialが存在する
[ ] AgentがHuman用credentialを使用しない
[ ] Agent対象Projectが限定されている
[ ] Agentが許可されたWorkflow遷移のみ実行できる
[ ] Human ReviewをAgentが迂回できない
[ ] Agentの操作がRedmine上で識別できる
ことを確認する。
31. 最初に構築する現実的な構成
現在の利用者が2人程度なら、最初から複雑にしすぎる必要はない。
現実的には、
Redmine Server
systemd
├─ redmine-mcp-reader
└─ redmine-mcp-writer
から始めればよい。
それぞれ、
Reader
├─ read-only MCP
└─ read-only Redmine credential
Writer
├─ write-enabled MCP
└─ limited writer Redmine credential
とする。
AI Agent Runner が実際に導入された段階で、
redmine-mcp-agent
を追加する。
Admin は必要性が出るまで作らなくてもよい。
まとめ
Redmine と MCP の権限管理では、大掛かりな独自認可基盤を最初から作る必要はない。
基本構造は、
systemd service
↓
権限別 MCP Server
↓
権限別 API Key
↓
Redmine User
↓
Redmine Role
↓
Workflow / Project Permission
で成立する。
特に重要なのは、
- MCP を専用 OS ユーザーで動かす
- Reader / Writer / Agent を credential レベルで分離する
- MCP 側だけでなく Redmine 側でも制限する
- Agent と Human を別 principal として扱う
- Agent が変更可能な Workflow State を限定する
- 高権限 Admin MCP を常用しない
- 常駐サービスでは MCP Server のバージョンを固定する
- systemd の起動確認だけでなく、許可・拒否の双方を検証する
- Redmine Journal を監査情報として利用する
という点である。
小規模な段階では、
Reader
Writer
だけで十分であり、
開発基盤が Agent 化された時点で、
Agent
を追加すればよい。
この方式なら、現在の数人規模の ChatGPT + Redmine MCP 環境から、将来の Agent Handler / Agent Runner を含む開発基盤まで、同じ認可モデルのまま段階的に拡張できる。