キーワードを入力して検索

📝

自作MCPサーバーを69ツールまで育てた話 ― 設計・セキュリティ・運用の実録

齊木祐介 齊木祐介 968 views

はじめに

⚠️ 本記事のコードはサンプルです。実際の認証情報・設定値は含まれていません。

複数の自社サービス(6つのPod)を運営していると、Claude Codeから「あのサービスのDB確認して」「エラーログ見て」「デプロイして」が日常になります。

最初はその都度SSHしたりMCPツールをバラバラに使っていましたが、統合MCPサーバー「mcp-mothership」を作って育てた結果、69ツール・27ファイル・4,629行の規模になりました。

この記事では、設計判断・セキュリティ対策・運用で学んだことを共有します。

全体構成

mcp-mothership/
├── server.js              # デュアルモード(stdio / HTTP)
├── config/pods.json       # Pod別接続情報
├── lib/
│   ├── config.js          # 環境変数・設定管理
│   ├── ssh.js             # SSH接続ヘルパー
│   ├── db/                # MySQL / SQLite / Mothership専用
│   ├── tools/             # ツール定義(11カテゴリ)
│   └── handlers/          # ハンドラー実装(tools/と1:1対応)

ポイント: tools/ と handlers/ を1:1で分離。ツール定義(名前・説明・パラメータ)とビジネスロジックを完全に分けることで、ツール追加が楽になります。

69ツールの内訳

| カテゴリ | ツール数 | 例 |
|---------|---------|-----|
| Pod管理 | 5 | pod_list, pod_detail, pod_compare |
| コード分析 | 8 | code_tree, code_search, code_functions |
| KPI・収益 | 8 | kpi_summary, kpi_trend, revenue_detail |
| DBクエリ | 5 | db_query, db_tables, db_execute |
| ログ分析 | 5 | logs_error, logs_access, logs_search |
| デプロイ | 7 | deploy_file, deploy_verify, vps_exec |
| AI社長 | 6 | ceo_decisions, ceo_analyze, ceo_report |
| ヘルスチェック | 4 | health_all, health_ssl, health_dns |
| ファイル操作 | 3 | file_create, file_edit |
| スキル管理 | 3 | skill_list, skill_create, skill_execute |
| Google Workspace | 15 | gmail, calendar, drive, search console |

最初は10ツール程度でしたが、「これもMCPから触れたら便利」を繰り返して69まで増えました。

設計で大事だったこと

1. デュアルモード(stdio / HTTP)

// server.js
if (process.argv.includes('--http')) {
  // Express + Bearer Token認証
  // VPSでPM2デーモンとして稼働
} else {
  // stdio: Claude Codeのローカル接続用
}

ローカルではstdio、VPSではHTTPで同じコードが動きます。PHPのAIチャットパネルからはREST API (/api/tool) 経由で呼べるので、MCPクライアントがなくても使えます。

2. VPSローカルモード

VPS上で動作する時はSSHをスキップして直接コマンド実行します。

// ssh.js
function isLocalMode() {
  return process.env.VPS_HOST === '127.0.0.1';
}

// ローカルなら bash -c で直接実行、リモートなら SSH

これで同じコードがローカル開発とVPS本番の両方で動きます。

3. ハンドラーの返却形式の統一

初期は返却形式がバラバラでした。

// pod.js - 生データを返す
return { pods: [...] };

// health.js - MCPフォーマット
return { content: [{ type: 'text', text: '...' }] };

server.js で isMcpFormat チェックを入れて両方に対応させました。REST APIは自動アンラップして生JSONを返します。最初から統一すべきでしたが、後方互換を保ちつつ対応できました。

セキュリティ: 15フェーズ修復の教訓

MCPサーバーは VPSにSSHしてコマンドを実行する という性質上、セキュリティが最重要です。45件の脆弱性を修正しました。

パスワード管理

// Before: pods.json にパスワード直書き 🚨
{ "password": "actual_password_here" }

// After: 環境変数名を指定
{ "password_env": "MOTHERSHIP_DB_PASSWORD" }

resolvePassword() で環境変数から解決する方式に変更。

SQLインジェクション防止

// validateTableName() - テーブル名のホワイトリスト検証
function validateTableName(name) {
  if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
    throw new Error('Invalid table name');
  }
}

// INTO OUTFILE / INTO DUMPFILE / LOAD_FILE をブロック

コマンドインジェクション防止

// shellEscape() - シングルクォートエスケープ
function shellEscape(str) {
  return "'" + str.replace(/'/g, "'\\''") + "'";
}

// vps_exec - 許可パスの制限
const ALLOWED_PATHS = ['/var/www/', '/var/log/'];
const BLOCKED_PATHS = ['/etc/', '/root/', '~/.ssh/', ...];

セキュリティゲート(環境変数制御)

MOTHERSHIP_ALLOW_WRITE=false  → DB書き込み系ツール無効
MOTHERSHIP_ALLOW_DEPLOY=false → デプロイ系ツール無効

デフォルトは全部 false。明示的に有効化しない限り危険な操作はできません。

Google Workspace統合(15ツール追加の実例)

直近で15ツールを一気に追加した流れ:

  • CLIラッパー作成 — 既存のPHP Google APIクライアントを php google-api-cli.php <service> <action> で呼べるように
  • MCPハンドラー — SSH経由でCLIラッパーを呼び出し、JSON返却をパース
  • ツール定義 — 15ツール分のスキーマ定義
MCPハンドラー → SSH → php google-api-cli.php gmail summary → 既存APIクライアント → JSON

既存資産(PHPのGoogle APIクライアント)を捨てずにMCPから呼べるようにしたのがポイントです。全部Node.jsで書き直す必要はありません。

運用で学んだこと

sshExec() の返却値に注意

// sshExec() は stdout文字列を直接返す
const result = await sshExec('ls /var/www/');
// result は文字列。result.stdout ではない!

mysqlQuery() は常に同じ形式

const result = await mysqlQuery('mothership_db', sql);
// 常に { columns: [], rows: [], count: 0 }
// getRows(result) で安全に取得

PM2のrootとdeploy問題

sudo pm2 と pm2 は 別のPM2インスタンス です。同名プロセスが両方に存在するとポート競合(EADDRINUSE)が発生します。本番はdeployユーザー側に統一しました。

まとめ

  • 小さく始めて育てる: 10ツールから69ツールへ。必要になったら追加
  • セキュリティはデフォルト安全: 環境変数ゲートで書き込み・デプロイを制御
  • 既存資産を活かす: PHPのAPIクライアントをCLIラッパー経由でMCPに統合
  • tools/ と handlers/ の分離: ツール追加のコストを最小化

MCPサーバーは一度作ると、Claude Codeでの開発体験が劇的に変わります。「SSHしてコマンド打って結果をコピペして…」がなくなり、自然言語で全部できるようになります。

齊木祐介

Discussion

0
または

8文字以上で入力してください

または

パスワードをリセット

登録したメールアドレスを入力してください。パスワードリセット用のリンクをお送りします。