植物研究者がMCP・Docker・n8n・WordPress連携を組み上げた構成と失敗記録
先日、ChatGPTに2枚の画像を貼り、次のように指示した。
「この2枚でテストして。WordPressとSNSの下書きまで。まだ公開はしないで」
少し前なら、画像をWordPressへアップロードし、本文のどこへ入れるか考え、アイキャッチを設定し、X、Bluesky、Instagram向けの文章を作り、それぞれの管理画面で下書きを作る必要があった。
今回は、ChatGPT上の一つの会話から、画像の転送、本文内への配置、アイキャッチ設定、SNSごとの文章と画像の割り当て、Bufferへの下書き登録まで進んだ。
しかも、指定どおり公開はされず、下書きで止まった。
試行錯誤の結果が処理速度に現れ、初期のテストと比べて処理時間は約10分の1になった。
ChatGPTがその日だけ急に賢くなったわけではない。それまでに、サーバー上の役割を分け、権限を制限し、失敗をテストや専用ツールへ変えてきた。その積み重ねが、ようやく目に見える速度として表れたのだと思う。
この記事は、エンジニアが完成度の高いシステムを設計した話ではない。
私は植物研究が本業で、ソフトウェア開発は専門外である。Linuxコマンドは必要な都度調べ、DockerやGitHubも、ChatGPTの説明と実行結果を確認しながら扱っている。
その程度の技術力から、ChatGPTと相談しながらVPSを借り、設定を間違え、サーバーを止めかけ、構成を何度も直しながら、自分用のAI基盤を作ってきた。
同じように、「プログラムは一から書けないが、AIの説明を読み、コマンドの結果を確認しながらなら進められる」という人に伝わる形で、構成と失敗を整理してみる。
なぜサーバーを作ったのか
私の課題は、アイデアが出ないことではなく、思いついたものを実際に使える形へ変えるまでが重いことだった。
私は、最初からある程度の完成形や理想形を思い描けるタイプだ。その理想形が見えると、必要な知識体系や作業も一緒に見える。
ブログ記事なら、文章を書く以外にも、写真の選定、画像加工、WordPressへの登録、SEO項目、SNS展開まで考えてしまう。アプリなら、UI、認証、データベース、公開方法、バックアップまで気になる。
その結果、思考の速度に対して、実装と公開の速度が追いつかない。
また、多くの先人のアイデアがあふれる中で、自分に必要な情報だけを集めた知識基盤を作り、横断検索や情報同士の関連性から新しい発想を生み出せるようにしたかった。
これらを実現する事例も知っており、自分用にパーソナライズする案も考えていたが、そのための基礎知識が足りず、後回しになっていた。
そこで、私は「何を作りたいか」「何が使いやすいか」「どこまで公開してよいか」を判断するが、設計、実装、テスト、反復作業はAIへ寄せることにした。
構成としては、ChatGPTを作業画面、VPSを実行環境、Vaultを長期記憶として使う。
ChatGPT:会話、指示、確認
VPS:コード、DB、自動処理の実行
Vault:会話、判断、資料、履歴の保存
GitHub:変更内容を人が確定する境界
最初から巨大なシステムを作ろうとしたわけではない。最初の目的は、ChatGPTで話した内容を後から検索できるよう、Markdownで保存することだった。
そこから「サーバー上の資料もChatGPTから直せたら便利」「コードも修正できたら早い」「WordPressの下書きまで作れたら楽」と、実際に使いながら広がっていった。
前提となる環境
構築時点の主な環境は次のとおり。
| 項目 | 構成 |
|---|---|
| VPS | XServer VPS 4GB |
| OS | Ubuntu 24.04 LTS |
| 管理ユーザー | 非rootユーザー、sudo利用 |
| ログイン | SSH鍵認証、root直接ログイン禁止 |
| コンテナ | Docker、Docker Compose |
| 常駐管理 | systemd |
| ソース管理 | GitHub private repository |
| 主な実装 | Python 3.12 |
| 軽量DB | SQLite |
| n8n用DB | PostgreSQL 16 |
| 文書 | Markdown |
秘密情報はGitへ含めず、.envやsystemdのEnvironmentFileなど、サーバー上の保護された場所へ置いている。
各サービスも原則として127.0.0.1だけで待ち受ける。ChatGPTから使うMCPはOpenAI Secure MCP Tunnelを通し、内部APIや管理画面をそのままインターネットへ公開しない構成にした。
現在の全体構成
現在は、役割ごとにサービスを分けている。
ChatGPT
|
+-----------------+-----------------+
| | |
Knowledge MCP Development MCP Publishing MCP
文書・知識 開発操作 WordPress・SNS
| |
Vault Development Runner
Docker・長時間処理
Vault Capture PWA -> Knowledge MCP -> Vault
n8n -> 定期処理・Webhook・監視
主な役割は次のとおり。
Knowledge MCP
Vault内のMarkdownを検索、作成、更新、移動する。正式記録、更新前履歴、アーカイブを管理する。
Development MCP
Git管理領域のファイル、Git差分、テスト、ログなど、開発に必要な操作を行う。
Development Runner
Docker Composeのbuildやrestart、長時間ジョブを担当する。Development MCP自身の外側に置く。
Publishing MCP
WordPressの記事、画像、SEO項目と、Buffer経由のSNS下書きを扱う。
n8n
定期処理、Webhook、監視など、人がその場で会話しなくても進む処理を担当する。
Vault Capture PWA
Android Chromeの共有メニューから記事を取り込み、コメントやSNS候補フラグを付けてVaultへ保存する。
最初からこの構成を設計できていたわけではない。実際には、失敗するたびに「これは同じサービスに持たせない方がよい」と分けた結果である。
1. 最初はKnowledge MCPだけだった
最初に作ったのは、Markdown中心のVaultをChatGPTから操作するKnowledge MCPだった。
主な機能は次のとおり。
- ファイル検索、一覧、読取
- Markdownの新規作成
- SHA-256確認付き更新
- 更新前ファイルのHistory保存
- hash確認付き移動
- 完全削除ではなくArchiveへの移動
- テンプレートからの文書作成
既存ファイルを更新する際は、事前に取得したSHA-256を要求する。読み取った後に別の処理で内容が変わっていればhashが一致せず、古い内容で上書きしない。
非エンジニア向けに言えば、「さっき読んだファイルが今も同じ状態か確認してから書き換える」仕組みである。
この時点では、Knowledge MCPにDockerやGitの操作まではさせなかった。文書管理とサーバー開発では、必要な権限が違いすぎるためである。
2. 開発用にDevelopment MCPを分けた
次に、ChatGPTからGit管理領域のコードや設定を変更するDevelopment MCPを作った。
主な機能は次のとおり。
- ディレクトリとファイルの読取・変更
- Git status、diff、commit
- Ruff、mypy、pytest
- 許可された軽量コマンドの実行
- ログの読取
- Development Runnerへの処理依頼
ここで重要だったのは、ChatGPTへサーバー全体を自由に触らせなかったことだった。
書込可能なルートをallowlistで限定し、Knowledge Vaultはread-only mountにした。@@INLINE0@@、@@INLINE1@@、credential、tokenなどを含むパスは読み取れないようにした。
任意のshellも許可していない。許可済みのコマンドと引数だけを実行し、bash -cや自由な外部通信は拒否する。
AIへ「サーバーを操作する権限」を渡したというより、「決めた作業だけを行える専用工具」を渡した形に近い。
3. Docker操作は外側のRunnerへ分離した
Development MCP自身もDockerコンテナとして動いている。
そのため、自分自身のbuildやrestartを直接実行すると、処理途中で接続が切れる。また、Development MCPへDocker socketを直接渡すと、権限が強くなりすぎる。
そこで、Docker操作と長時間処理をDevelopment Runnerへ分けた。
Runnerは次を担当する。
- Docker Composeの状態確認
- build、up、restart、stop
- Development MCPの更新
- 長時間ジョブの投入
- 状態、ログ、キャンセル
- rollback
ジョブはIDを発行し、状態とログをSQLiteへ保存する。Runnerを再起動しても過去のジョブを確認できる。
2026年8月3日、開発開始から約1週間の時点では、110件のジョブ履歴が残っている。最初はその場限りだったサーバー操作が、後から追える作業履歴へ変わった。
4. GitHubを「最後に確定する場所」にした
ChatGPTからコードを修正できても、そのままmainへ反映させる構成にはしなかった。
標準的な流れは次のとおり。
ChatGPTで要件と受入条件を整理
↓
Development MCPでコードを変更
↓
Ruff・mypy・pytest
↓
ローカルでGit commit
↓
chatgpt/*ブランチへpush
↓
GitHub Pull Request
↓
私が差分とCIを確認してmerge
↓
VPSのmainをfast-forwardで同期
↓
対象サービスだけ更新
↓
health checkと実操作確認
AIはmainへ直接pushできない。force pushもできない。作業ツリーがcleanで、事前に確認したHEADと現在のHEADが一致する時だけ、chatgpt/で始まるブランチへpushできる。
非エンジニアの感覚で言えば、AIが変更案を作り、最後の確定ボタンは自分が持つ構成である。
開発開始からPull Requestは#1から#30まで作成され、古いブランチを再利用して競合した#2を除き、29件をマージした。
大きな完成版を一度に作るのではなく、基盤、権限、バックアップ、n8n、WordPress、Buffer、複数画像と、小さなPRを積み重ねたことも、非エンジニアには進めやすかった。
5. n8nに全部やらせるのをやめた
当初は、WordPressやSNSへの投稿もすべてサーバー内に置いたn8n経由にするつもりだった。
ChatGPT -> n8n -> WordPress / SNS
n8nは、決められた処理を繰り返すことには強い。定期処理、Webhook、外部サービスの接続には向いている。
一方、記事作成は会話の途中で何度も変わる。
タイトルを変え、段落を追加し、画像を入れ替え、SNSごとに文面を調整し、最後に「今日は公開せず下書きまで」と判断することもある。
この対話を早い段階で固定されたワークフローへ入れると、かえって扱いにくかった。
そこで、対話しながら進める発信作業はPublishing MCPへ分けた。
ChatGPT -> Publishing MCP -> WordPress / Buffer
n8n -> 定期処理・Webhook・監視・無人処理
n8nをやめたのではなく、向いている仕事へ戻した。
Publishing MCPでは、記事とSNS投稿をPublicationという単位でSQLiteへ保存する。Publicationには、本文、SEO、画像、SNS文面、revision、外部サービス上のIDや状態をまとめて持たせる。
更新のたびにrevisionを増やし、古いrevisionを基にした公開や更新は拒否する。会話中に内容が変わっても、どの版を外部へ送ろうとしているのかを明確にするためである。
6. 公開系は「準備・確認・実行」を分けた
WordPressやSNSへの書込みでは、下書き作成と公開を同じ操作にしなかった。
prepare
↓
preview
↓
人が確認
↓
confirmed execution
↓
外部サービスから読み戻す
↓
内部記録と照合する
例えばWordPressでは、APIが成功しただけでは完了とみなさない。
下書き作成後にWordPressから再取得し、次を確認する。
- タイトル
- 本文のhash
- 抜粋
- slug
- カテゴリー、タグ
- draft status
- CocoonのSEO項目
BufferもX、Bluesky、Instagramを別々の対象として記録する。Xだけ成功し、Instagramが失敗した場合は、失敗した対象だけ再試行できる。
自動化というと、確認なしで最後まで進めるイメージがある。しかし、私には「人が見る場所が明確で、それ以外を自動化する」構成の方が使いやすかった。
7. ChatGPTに貼った画像をそのままWordPressへ送る
最初の画像投稿は、公開HTTPS URLから画像を取り込む方式だった。
しかし、ChatGPTへ添付した画像を、いったん別の場所へ公開してURLを作るのは手間がかかる。Base64へ変換する方法もあるが、画像を巨大な文字列へ変えるため扱いにくい。
この直接転送経路を作る前は、画像を断片的に送る力技で対応しており、投稿作業には現在の約10倍の処理時間が必要だった。正直、発狂しそうだった。
現在は、ChatGPTの添付ファイルをMCPのfile parameterとして受け取り、そのままWordPressへアップロードする。
ChatGPT添付画像
↓
Publishing MCP
↓
容量・MIME type・binary signatureを確認
↓
対象WordPressのメディアライブラリへ保存
↓
恒久HTTPS URLをPublicationへ記録
↓
WordPress本文とBufferで再利用
一時的なダウンロードURLはDBやログへ保存しない。
複数画像の途中で失敗した場合は、その処理中に作成したWordPress attachmentを削除し、以前のPublication revisionを維持する。
画像には次の状態を持たせた。
selected:使用するreserve:予備として残すrejected:使用しない
本文内では、導入段落の後、特定の見出しの前後、記事末尾などへ配置する。単純な「3段落目の後」ではなく見出しを基準にすることで、途中に文章を追加しても画像位置がずれにくい。
SNSの画像上限も事前に検証する。
- X:最大4枚
- Bluesky:最大4枚
- Instagram:最大10枚
Instagramの縦横比に合わない場合は、WordPressが生成した別サイズの画像から互換性のあるものを選ぶ。
分かる人向けに言えば、画像の編集状態、配置anchor、媒体別media ID、外部targetをPublication revisionへ持たせている。
非エンジニア向けに言えば、「どの写真を、どこで、何に使ったか」を、AIのその場の判断だけにせず、後から確認できる形にした。
実際に起きた失敗
失敗1:既存のKnowledge MCPとポートがぶつかった
超初歩的な話でお恥ずかしい限りだが、後学のために残す。
Development MCPの初期構成は、8000番ポートを使う想定だった。しかし、8000番ではすでにKnowledge MCPが動いていた。
新しい店を作ろうとして、既存の店と同じ住所を指定していたようなものだった。
Development MCPを18080番へ移し、起動前のポート競合検査を追加した。さらに、Compose project、systemd unit、Tunnel profile、データ保存先もサービスごとに分けた。
ここで分かったのは、「既存環境を壊さないで」とAIへ伝えるだけでは足りないことだった。
ポート、設定、データ、常駐サービスなど、何を分離するのかまで具体的に指定する必要がある。
失敗2:強そうなOAuth設定を選んで接続できなかった
Development MCP用のTunnelを設定した際、OAuthや動的クライアント登録を前提とするサンプルを選んだ。
認証が多い方が安全そうに見えたためである。
しかし、MCP側はそのOAuth metadataを提供しておらず、診断では存在しないURLへアクセスして404になった。MCP本体は正常なのに、Tunnelのdoctorだけが失敗する状態だった。
最終的には、OAuth metadataを要求しないremote no-auth profileへ変更し、MCP本体はlocalhostへ閉じ、外部との境界はOpenAI Secure MCP Tunnel側へ置いた。
設定項目が多いことと、安全であることは同じではない。実際の構成に合った境界を作る方が重要だった。
失敗3:Runnerが自分自身を止めた
Development MCPを更新する処理で、Composeの依存関係によりDevelopment Runnerまで再作成された。
つまり、再起動作業を担当している人が、作業中に自分自身の電源を切った。
修正として、Development MCPだけを更新する時は--no-depsを強制した。
docker compose up -d --no-deps development-mcp
さらに、Runner自身のrestartやstopは、Runner APIから拒否するようにした。
AIへ強い権限を渡す場合は、「何ができるか」だけでなく、「自分自身や制御系を変更できるか」も分けて考える必要がある。
失敗4:Gitの状態表示が実体とずれた
GitHubのmainを取得しても、origin/mainの追跡参照が古いまま残ることがあった。
FETCH_HEADだけを使って更新していたことが原因で、remote tracking refも明示的に更新するよう修正した。
また、古いCodex用ブランチを再利用したPRはmainと競合し、閉じて最新mainから作り直した。
この後は、毎回最新mainから小さなブランチを作り、main同期はfast-forward onlyに限定した。
失敗5:Bufferの下書きが送信状態へ移らなかった
Bufferで作ったdraftを即時送信へ変更する処理では、作成時専用のchannelIdを更新時にも送っていた。また、下書きを解除する指定も不足していた。
その結果、draftのまま残ったり、draftなのに同期済みと判断したりする可能性があった。
修正後は、edit時に@@INLINE0@@を除外し、必要な場合は@@INLINE1@@を指定した。さらに、その状態遷移を再現する回帰テストを追加した。
外部APIは、「投稿」という一つの物体だけを見るのではなく、draft作成、更新、送信という状態の変化ごとに確認する必要があった。
バックアップは「取る」だけでは足りなかった
コードはGitHubにあっても、次のデータはGitだけでは戻らない。
- VaultのMarkdown
- Development RunnerのSQLite
- Publishing MCPのSQLite
- n8nのPostgreSQL
- n8nの暗号化設定
- systemd unit
- Tunnel設定
- runtime environment
そのため、これらを日次バックアップへ含め、manifest、SHA-256、repository HEADも記録するようにした。
さらに、最新バックアップを本番とは別の一時領域へ展開し、復元可能か確認するrestore rehearsalも用意した。
バックアップファイルが存在することと、実際に戻せることは別だからである。
ただし、現在の主なバックアップ先は同一VPS内であり、VPSやストレージ全体の障害に備えるoff-site backupは今後の課題である。
同じ程度の技術力から再現するなら
現在の構成を一度に作ることは勧めない。私自身も、最初からこの形を設計できたわけではない。
次の順序なら、各段階で止めても実用になる。
1. VPSとGit管理を整える
最初に、Ubuntu VPS、非rootユーザー、SSH鍵認証、Firewall、Docker、private GitHub repositoryを用意する。
サーバー上で直接ファイルを編集し続けず、構成をGit管理して、変更前後を比較できる状態にする。
2. 最初のMCPは読み取り中心にする
特定フォルダの一覧、ファイル読取、health check程度から始める。
いきなり削除、Docker、任意commandを許可しない。
3. 書込範囲を限定してGit差分を扱う
allowlist配下だけ書き込めるようにし、.envやSSH鍵などは読めないようにする。
Git status、diff、test、local commitまで通す。
4. AIによる変更と、人による確定を分ける
AIはchatgpt/*ブランチへpushし、人がPull Requestを確認してmergeする。
公開、削除、main反映を同じ操作にしない。
5. Docker操作を外部Runnerへ分ける
Docker socketはRunnerだけへ渡し、対象repositoryとCompose projectを固定する。
長時間処理はジョブ化し、状態とログを残す。
6. 外部サービスは下書き作成から始める
WordPressやSNSでは、最初から完全自動公開を目指さない。
接続確認、preview、draft作成、read-back verificationを通してから、publishやscheduleを別操作として追加する。
7. 一度起きた失敗をテストへ変える
手作業で直して終わると、次回も同じ問題が起きる。
失敗した条件を、テスト、validation、専用ツールの制約へ入れる。今回の構築で速度が上がった最大の理由は、この蓄積だった。
AIへ依頼する時に有効だった伝え方
非エンジニアがAIとサーバーを作る場合、単に「作って」と頼むより、次を明示した方が安定した。
- 既存サービスは変更しない。変更が必要な場合は、まず提案だけさせる
- 変更前に現在の状態を確認する
- 対象ディレクトリと対象サービスを限定する
- 一度に変更する範囲を小さくする
- 受入条件を先に決める
- 実行するテストを決める
- 失敗時の戻し方を用意する
- 秘密情報を表示・記録しない
- 公開や削除は明示承認まで行わない
また、長いshell commandを毎回コピーするより、一度成功した処理をscriptやMCP toolへ変える方が安全だった。
最初はChatGPTからコマンドを受け取り、自分でVPSへ貼り付けていた。しかし、引用符や改行、sudo入力で失敗することもある。同じ処理を二回使うなら、専用ツールへ昇格させる方がよい。
現在も完成してはいない
ここまで書くと、ある程度進んだシステムに見えるかもしれないが、まだ構築途中である。
- Development RunnerはDocker socketへアクセスするため権限が強い
- off-site backupが未完成
- Vault Capture PWAのHTTPS公開が未完了
- WordPressとBufferは下書き中心に試験中
- credential期限の監視が未実装
- 障害通知や定期health summaryが未完成
公開処理を完全自動化していないのは、未完成だからだけではない。
私は、人間を処理から完全に外したいわけではない。目的、優先順位、見た目、公開可否は自分で判断し、その間の反復作業をAIへ任せたい。
AI:設計案、実装、テスト、整理、変換
人:目的、評価、承認、公開判断
この分担が、自分には最も扱いやすかった。
一度目の失敗を、二度目から減らす
画像2枚の投稿テストが短時間で終わった時、モデルの性能以上に、積み重ねた構造の効果を感じた。
それまでには、ポート競合、Tunnel設定の選択ミス、自分自身を止めたRunner、古いGitブランチの競合、Bufferの状態遷移不具合があった。
それぞれを、専用ツール、権限制約、回帰テスト、読み戻し確認、履歴保存へ変えた。
一度目は、人が考えて解決する。
二度目からは、仕組みが先に検出する。
VPSは、単なるデータ置場から、AIが仕事をする作業環境へ変わりつつある。ただし、AIが働けるようになったのは、自由に何でも操作できるようにしたからではない。
役割、権限、記録、確認地点を決めたからである。
非エンジニアでも、すべてを理解してから始める必要はなかった。
小さく作り、実際に使い、失敗を残し、次の仕組みへ変える。
少なくとも私は、その方法で、数日間に29件のPull Requestを積み上げ、画像付きのWordPress・SNS下書きまで一つの会話から処理できるところまで作ることができた。
最後に
3つのブログと9つのSNSアカウントへの自動投稿は、すでに機能している。この記事もChatGPT経由で投稿している。
知識集積基盤には、順調に情報が積み上がっている。いつか、これらの情報同士のつながりから新しいひらめきが生まれる。そんな確信に近い感覚があるが、単なる「開発ハイ」による錯覚かもしれない。
それでも、自分が必要だと思った知識をすぐに取り出せる感覚は、とても良い。
ここ数日の開発は、新しいことの積み重ねだった。客観的に見れば、地味な作業の連続だったかもしれない。それでも、いつか日の目を見るだろう。
その日まで、今の運用と開発を休みなく続けていこうと思う。

コメント