😃

MCPってなんぞやって疑問を解決できる

に公開

🚀 MCP/MCPO/OpenWebUI/Zen MCP を“社内導入”目線で徹底整理(実装例つき)

「OpenWebUIはUIって言うけど、もうスーパーUIでは?」
「MCPのホスト/クライアント/サーバ、MCPOを噛ませた接続は?」
——概念の整理から、実際に動く Compose まで一気通貫でまとめます。


🧭 この記事でわかること

  • MCPの三層(ホスト/クライアント/サーバ)の正しい頭の置き方
  • OpenWebUIが“UI以上”に担っていること(LLM接続・履歴・RAG・OpenAPIツール連携)
  • MCPOを噛ませて OpenWebUI →(OpenAPI)→ MCPサーバ群 を使う構成
  • Zen MCP(多機能ツール箱)と Fetch MCP(汎用RESTクライアント)を一括でOpenWebUIに接続
  • 本番運用の勘所(SSE常駐・ヘルスチェック・鍵・権限・監査)

📚 用語の辞典(最短で腹落ちする定義)

MCP(Model Context Protocol)

  • 目的:LLMが外部ツールを“標準手順”で見つけて呼び出すためのプロトコル

  • 三層

    • ホスト(Host):アプリ側の“司令塔”。複数のクライアントを管理(例:Claude Desktop 等)
    • クライアント(Client)1対1でサーバに接続するセッション単位
    • サーバ(Server):ツール/リソース/プロンプトの提供者(例:Zen MCP / Fetch MCP / 社内独自MCP)

OpenWebUI(“スーパーUI”)

  • 名称は“UI”だが、実際は UI+アプリ基盤

    • LLM接続(OpenAI/ Azure / OpenRouter / LM Studio / Ollama / LocalAI / vLLM…)
    • チャット履歴DB(SQLite/Postgres)
    • 知識ベース(RAG;VectorDB統合)
    • OpenAPI準拠ツールの読み込み・実行(※MCPそのものは直接は話せない)

MCPO(MCP→OpenAPI Proxy)

  • 役割:MCPサーバ群をOpenAPI化して外部アプリから使えるようにする“ゲートウェイ”
  • 中身:MCPOは実質 MCPホストの役。内部でサーバごとにMCPクライアントを起動(1対1)して接続し、それらをOpenAPIとしてエクスポート
  • ポイントツール選定の意思決定はしない(どのエンドポイントを叩くかは上位アプリ=OpenWebUI側の役割)

Zen MCP(MCPサーバ)

  • 多機能ツール箱planner / codereview / debug / tracer / testgen / …
  • バックエンドLLMはOpenAIでもローカルでもOK
  • 社内向けに“DB/ファイル等の独自ツール”を増やしていける

Fetch MCP(MCPサーバ)

  • REST API呼び出し特化:GitHub/Backlog/Jira/Slack/自社APIなど、HTTPで叩けるなら広く扱える

🧩 全体構成(アーキテクチャ図)

[人間/ブラウザ]
      │
      ▼  (OpenAPI)
OpenWebUI ─────────→  MCPO(OpenAPIサーバ + MCPホスト)
                          ├─ (MCPクライアントA) → Zen MCP(MCPサーバ:社内ツール/多機能)
                          └─ (MCPクライアントB) → Fetch MCP(MCPサーバ:REST汎用)
  • 会話生成は OpenWebUI 側の設定LLM(OpenAIやローカル)が担当
  • ツール実行は OpenWebUI →(OpenAPI呼び出し)→ MCPO →(MCP)→ 各サーバ

🛠️ 実装:Docker Compose 一式

スタック:OpenWebUI + OpenAI(クラウド) + MCPO + Zen MCP + Fetch MCP
(ローカルLLMに切り替えたい場合の注記も併記)

📁 ディレクトリ構成(例)

project-root/
├─ docker-compose.yml
├─ .env
├─ mcpo/
│   └─ config.json
└─ zenmcp/
    └─ Dockerfile

1) .env(APIキーや基本設定)

# === LLM: OpenAI を使う場合 ===
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx
# OpenWebUIが叩く先(LM Studio/LocalAI等のOpenAI互換に切替も可能)
OPENAI_API_BASE_URL=https://api.openai.com/v1

# === MCPO ===
MCPO_API_KEY=mcpo-secret        # OpenWebUI→MCPO の認証ヘッダに使う

# === Zen MCP(任意)===
DEFAULT_MODEL=gpt-4o-mini       # Zen MCP側の既定モデル
LOG_LEVEL=INFO

ローカルLLMにしたいときは OPENAI_API_BASE_URL
http://lmstudio:1234/v1http://localai:8080/v1 に差し替えればOK。


2) mcpo/config.json(複数MCPサーバの集約設定)

{
  "mcpServers": {
    "zenmcp": {
      "type": "sse",
      "url": "http://zenmcp:3001/sse"
    },
    "fetch": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-fetch"]
    }
  },
  "security": {
    "apiKey": "${MCPO_API_KEY}"
  }
}
  • zenmcpSSE/HTTP で待ち受ける想定
  • fetchstdio で子プロセス起動(Nodeが必要)
  • MCPOはこの定義を読み込み、/zenmcp/fetch 配下にOpenAPIを公開

3) zenmcp/Dockerfile(SSE/HTTP 常駐で起動)

FROM python:3.11-slim

RUN apt-get update && apt-get install -y --no-install-recommends \
    git curl ca-certificates && rm -rf /var/lib/apt/lists/*

# 依存解決を速めたいなら uv を採用(任意)
RUN pip install --no-cache-dir uv

WORKDIR /app
# 公式リポジトリを取得(固定タグにする運用推奨)
RUN git clone https://github.com/BeehiveInnovations/zen-mcp-server.git . 
# 依存インストール
RUN uv pip install --system -r requirements.txt

EXPOSE 3001
ENV PORT=3001

# SSE/HTTP モードで常駐(実装に合わせて調整)
CMD ["bash","-lc","python -u server.py --sse --host 0.0.0.0 --port 3001"]

重要:MCPをDocker常駐させるなら stdioではなくSSE/HTTP で立てるのが安定(stdioは“接続が切れたら終了”特性)。


4) docker-compose.yml

services:
  openwebui:
    image: ghcr.io/open-webui/open-webui:latest
    container_name: openwebui
    restart: unless-stopped
    ports: ["3000:8080"]
    environment:
      OPENAI_API_KEY: ${OPENAI_API_KEY}
      OPENAI_API_BASE_URL: ${OPENAI_API_BASE_URL}
      ENABLE_PERSISTENT_CONFIG: "true"
    depends_on: [mcpo]
    volumes:
      - openwebui_data:/app/backend/data

  # Zen MCP(SSEで待受)
  zenmcp:
    build:
      context: ./zenmcp
      dockerfile: Dockerfile
    container_name: zenmcp
    restart: unless-stopped
    ports: ["3001:3001"]
    environment:
      OPENAI_API_KEY: ${OPENAI_API_KEY}
      DEFAULT_MODEL: ${DEFAULT_MODEL}
      LOG_LEVEL: ${LOG_LEVEL}
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3001/healthz"]
      interval: 20s
      timeout: 3s
      retries: 5

  # MCPO(MCP→OpenAPI 変換 & 集約)
  mcpo:
    image: ghcr.io/open-webui/mcpo:main
    container_name: mcpo
    restart: unless-stopped
    ports: ["8000:8000"]
    environment:
      MCPO_API_KEY: ${MCPO_API_KEY}
    volumes:
      - ./mcpo/config.json:/app/config.json:ro
    command: >
      --port 8000
      --api-key ${MCPO_API_KEY}
      --config /app/config.json
    depends_on: [zenmcp]

  # Fetch MCP を“stdioサーバ”として子プロセス起動させるなら
  # MCPOから起動するので、別コンテナは不要。ただし Node が必要なら
  # 以下のようなランタイムを用意して MCPO に /usr/local/bin/npx を提供する方法もあり。
  node-runtime:
    image: node:20-alpine
    container_name: node-runtime
    command: ["sh","-lc","sleep infinity"]
    # MCPOコンテナに npx を提供したい場合は、ボリューム共有や単一コンテナ化を検討
    # 本例では単に“参考”として掲載(必須ではない)

volumes:
  openwebui_data:

補足

  • fetchMCPOが子プロセス起動するなら、MCPOコンテナ内に npx が必要。mcpo のDockerfileを自作し node を入れる or --command を別の方法に変える、など運用に合わせて調整してね。
  • あるいは Fetch MCPを独立コンテナで立て、SSE/HTTP で公開して MCPOから type: "sse" で接続する構成でもOK。

5) 起動手順

# ビルド & 起動
docker compose up -d --build

# 動作確認
docker compose ps
docker logs -f zenmcp
curl -s http://localhost:8000/docs | head   # MCPOのOpenAPIが生きてるか確認

OpenWebUIでの設定

  1. Settings → Tools → Manage Tool Servers → Add Connection

    • URL: http://mcpo:8000(Compose内ならサービス名、ホストなら http://localhost:8000)
    • Header: Authorization: Bearer <MCPO_API_KEY>
  2. ツール一覧(zenmcp / fetch)が同期 → 有効化して利用開始


🔒 本番運用の勘所

  • SSE常駐:MCPサーバは SSE/HTTP で常駐。stdioは“接続が切れると終了”なのでDocker常駐に不向き
  • レートと鍵:OpenWebUIの会話LLMとZen MCPの内部LLMを同じAPIキーにするとレート衝突しやすい(429)。用途別キーバックオフ
  • 権限分離:OpenWebUI(社内全体向けUI)に出すツールは読み取り中心+限定導線。IDE向けの強権ツールはZen MCP側
  • 監査ログ:ツールAPI(DB/FSなど)で “誰が何にアクセスしたか”を必ずロギング。後追い可能に
  • ネットワーク:全サービスを社内VPC/セグメントに閉じる。MCPOにはAPIキー必須
  • OpenWebUIのPersistentConfig:一部環境変数は初回起動時に内部DBへ固定。設定変更はUIから or 再初期化で

🧪 トラブルシュート早見表

  • zenmcp が再起動ループ:SSEじゃなくstdio起動になってない?server.py --sse --host 0.0.0.0 --port 3001 を確認
  • MCPO /docs が404--config のパス・フォーマット、mcpServerstype/url/command を再確認
  • OpenWebUIからツールが見えない:Tool ServerのURL/認証ヘッダ(Bearer)ミスが多い
  • 429/RateLimit:OpenWebUIとZen MCPで別APIキーに分ける or モデル負荷を調整
  • CORS/ネットワーク:社内DNS名/サービス名で解決できているか、ポート開放/Firewallもチェック

🧷 付録:ローカルLLMに切り替えるとき

  • .envOPENAI_API_BASE_URL をローカルのOpenAI互換に変更(例:http://lmstudio:1234/v1
  • 追加で lmstudio コンテナを起動し、OpenWebUIはそのURLを叩く
  • Zen MCP 側も同じベースURL/キーに切り替えて二系統のLLMを揃えると迷いが少ない
    (会話LLM=OpenWebUI、ツール内部LLM=Zen MCP、で別モデルにして分離運用もアリ)

✅ まとめ(要点だけ)

  • OpenWebUI は“UI以上”の スーパーUI(LLM接続・履歴管理・OpenAPIツール連携)
  • MCPホスト/クライアント(1対1)/サーバ の三層で考える
  • MCPO を噛ませると OpenWebUI→(OpenAPI)→ MCPサーバ群 が実現
  • Zen MCP(多機能ツール)+ Fetch MCP(REST汎用)をMCPOで集約し、OpenWebUIから一元利用
  • SSE常駐/鍵/権限/監査 を抑えれば、社内運用も十分実用的

Discussion