【前編】IBM OpenPages のMCP サーバーをIBM Bob に接続する ― 環境構築からローカル起動確認まで
はじめに
IBM が公式に公開している IBM/ibm-openpages-mcp-server を使うと、
AI エージェントからIBM OpenPages(GRC プラットフォーム)のオブジェクトを検索・作成・更新できるようになります。
この記事は2 本構成のシリーズの 前編 です。公式リポジトリをclone して、ローカルでMCP サーバーが起動するところまで を、コピペで再現できる形でまとめます。
| 記事 | 内容 |
|---|---|
| 前編(本記事) | 環境構築 → .env 設定 → コード修正 → ローカル起動確認 |
| 後編 | 起動ラッパー作成 → IBM Bob への登録 → 疎通確認 → ハマりどころ総括 |
IBM OpenPages とは
IBM OpenPages は、組織のリスク管理、コンプライアンス、監査といった業務を1 つの基盤にまとめる GRC(ガバナンス・リスク・コンプライアンス)プラットフォーム です。
IBM の製品ページでは、「スケーラブルなAI 搭載のGRC プラットフォーム」で、クラウドでもオンプレミスでも利用でき、必要なコンポーネントだけを選んで導入できるモジュール式の構成だと説明されています。また、オープンなAPI でBI ツールやデータ基盤と連携できるとされています。
この記事の手順で重要なのは、OpenPages が REST API を備えている 点です。MCP サーバーは、このREST API を呼び出してOpenPages のオブジェクト(Issue、Control、Risk など)を扱います。記事では、OpenPages のREST API にBasic 認証でアクセスします。
IBM Bob とは
IBM Bob は、IBM が提供する AI を活用したソフトウェア開発のパートナー(AI SDLC パートナー)です。
IBM の公式ドキュメントでは、既存のワークフローを補強し、実際のコードベースを理解・計画・改善する作業を支援するもの、と説明されています。
チャット形式で指示を出し、Bob が提案する操作を確認・承認しながら進めます。
Bob は、ファイルの読み書き、ターミナルでのコマンド実行といった操作に加えて、MCP を通じて外部のツールを使う ことができます。
この記事のシリーズでは、このMCP の仕組みを使って、Bob からOpenPages を操作できるようにします。
MCP(Model Context Protocol)とは
MCP は、AI システムが外部のツールやサービスとやり取りするための 標準化された通信プロトコル です。
MCP サーバーが「ツール」や「リソース」を公開し、Bob のようなMCP クライアントがそれを呼び出します。
今回のMCP サーバーは、OpenPages のREST API を、Bob が使える形のツールとリソースに包む役割を担います(全体の構成は後述の「全体構成」を参照してください)。
この記事で得られるもの
- Python 仮想環境上でOpenPages MCP サーバーを Local(stdio)モード で起動できる
- 私の環境で実際に遭遇した、公式リポジトリそのままでは動かない箇所2 つ(ログの stdout 汚染 / API パスのプレフィックス)と、その修正差分
全体構成
前編では、右側の main.py --mode local とOpenPages の接続までを作ります。run_local_bob.sh と Bob 側の登録は後編です。
公式リポジトリに 手を加える 手順を含みます(手順3)。これは私の検証環境で必要だった修正であり、すべてのOpenPages 環境で必要とは限りません。該当しない環境では手順3 の ② は不要です。
前提条件
必要な条件と、私の検証環境を次の表にまとめます。この記事の手順・出力は、すべて右列の環境で実施・取得したものです。
バージョンが異なる場合、挙動や出力が変わる可能性があります。
| 項目 | 必要な条件 | 私の検証環境 |
|---|---|---|
| IBM OpenPages | REST API にBasic 認証でアクセスできるインスタンス(URL・ユーザー名・パスワードが分かること) | 9.2.1(検証用にホストされたインスタンス。HTTP・Basic 認証でアクセス) |
| IBM Bob | 後編で使用します。前編では不要です |
2.2.3(macOS 版アプリ。Info.plist の CFBundleShortVersionString) |
| OS | Linux でも同様のはずですが未検証です | macOS 26.6.2(arm64) |
| Python | 公式の .python-version は 3.12
|
3.13.1(Homebrew 版。venv もこの版) |
| OpenPages MCP サーバー | IBM/ibm-openpages-mcp-server |
clone 時点の最新コミット 3bf3fda(Vulnerabilty Fix (#12)、2026-07-24) |
mcp(Python SDK) |
requirements.txt では >=1.9.4
|
2.3.0 |
fastapi / uvicorn
|
>=0.104.0 / >=0.24.0
|
0.162.0 / 0.54.0 |
httpx |
>=0.24.0 |
0.28.1 |
pydantic / pydantic-settings
|
>=2.5.0 / >=2.1.0
|
2.14.1 / 2.16.1 |
requirements.txt は mcp>=1.9.4 のように 下限のみの指定 なので、インストール時点の最新版が入ります。
バージョンを固定したい場合は、動作確認済みの pip freeze の結果を残しておくと再現しやすくなります。
OpenPages 9.2.1 は私が利用したインスタンスの版であり、他の版(たとえば SaaS 版や CP4D 版)での動作は確認していません。
手順3 の ② で触れる REST API のパス(/opgrc の要否)は、インスタンスの構成ごとに異なる 点にご注意ください。
表記について
OpenPages の接続情報は、この記事ではすべてダミー値で表記します。お手元の値に置き換えてください。
| 本文中の表記 | 意味 |
|---|---|
https://your-openpages-instance.com |
OpenPages の URL |
your_username / your_password
|
OpenPages のユーザー名 / パスワード |
/path/to/OpenPages-MCP |
作業ディレクトリ |
手順 1: リポジトリの取得とPython 仮想環境の作成
mkdir -p /path/to/OpenPages-MCP && cd /path/to/OpenPages-MCP
git clone https://github.com/IBM/ibm-openpages-mcp-server.git
cd ibm-openpages-mcp-server
仮想環境を作成して依存パッケージをインストールします。
python3 --version # 3.12 以上であることを確認
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
主要な依存パッケージとバージョンは、冒頭の「前提条件」の表のとおりです。
Local モードではFastAPI / uvicorn は使いませんが、requirements.txt はRemote モードと共通なのでそのままインストールします。
インストール後、起動オプションを確認します。
python main.py --help
私の環境の出力(先頭にJSON 形式のログ行が1 行出ますが省略しています):
usage: main.py [-h] [--mode {remote,local}] [--host HOST] [--port PORT] [--debug]
GRC MCP Server
options:
-h, --help show this help message and exit
--mode {remote,local}
Server mode: remote (HTTP) or local (stdio)
--host HOST Host to bind the server to (remote mode only)
--port PORT Port to bind the server to (remote mode only)
--debug Enable debug mode
--mode local がstdio で動くモードです。
手順 2: .env の設定
リポジトリには用途別の .env.example.* が用意されています。Basic 認証の最小構成をコピーして使います。
cp .env.example.minimal.basic .env
コピー直後の内容です。
# .env.example.minimal.basic の内容(コメントは一部省略)
OPENPAGES_BASE_URL=https://your-openpages-instance.com
OPENPAGES_AUTHENTICATION_TYPE=basic
OPENPAGES_USERNAME=your_username
OPENPAGES_PASSWORD=your_password
OPENPAGES_AUTH_MODE=server
SSL_VERIFY=true
これを、Local モードで動かすために次のように編集します。私が実際に使った項目は以下のとおりです(値はダミーです)。
# .env
OPENPAGES_BASE_URL=https://your-openpages-instance.com/openpages/
OPENPAGES_AUTHENTICATION_TYPE=basic
OPENPAGES_USERNAME=your_username
OPENPAGES_PASSWORD=your_password
OPENPAGES_AUTH_MODE=server
SSL_VERIFY=true
SERVER_MODE=local
LOG_LEVEL=INFO
LOG_FORMAT=json
DEBUG=False
HOST=0.0.0.0
PORT=8000
# 手順3 の ② で追加する設定(空文字にする)
OPENPAGES_API_PREFIX=
各項目の意味です。
| 変数 | 設定値 | 説明 |
|---|---|---|
OPENPAGES_BASE_URL |
OpenPages のURL | 私の環境ではコンテキストパス /openpages/ を含めたURL でした |
OPENPAGES_AUTHENTICATION_TYPE |
basic |
ユーザー名 + パスワードによるBasic 認証 |
OPENPAGES_AUTH_MODE |
server |
MCP サーバー自身の認証情報で全リクエストを処理する(ローカル・開発向け)。公式README では、user が既定でマルチテナント向け、server がオンプレ・ローカル・開発向けと説明されています |
SSL_VERIFY |
true |
自己署名証明書の開発環境では false にする |
SERVER_MODE |
local |
--mode を省略した場合の既定モード |
OPENPAGES_API_PREFIX |
(空) | 公式リポジトリには存在しない設定。手順3 で追加します |
.env にはパスワードが入ります。リポジトリの .gitignore に .env が含まれていることを確認し、コミットや記事・ログへの貼り付けをしないでください。
git check-ignore -v .env
手順 3: コードの修正(ハマりどころ ① ②)
ここまでの状態でそのまま動かすと、私の環境では2 点で問題が出ました。順に説明します。
ハマりどころ ①: ログがstdout に出力され、MCP プロトコルが壊れる
症状
Local(stdio)モードでは、stdout はJSON-RPC メッセージ専用です。クライアント(Bob)はstdout を1 行ずつJSON-RPC として解釈します。
ところが main.py は、モジュール読み込み時に use_stderr=False(= ログをstdout に出す) で setup_logging を呼んでいました。
--mode local が分かるのは argparse の後であり、それより前に出たログがstdout に混ざります。
実際に、修正前の main.py に initialize を送ってstdout の行を数えると、JSON-RPC 応答ではないログ行が1 行 混入していました(修正後は0 行。確認方法は手順4)。
このログは {"timestamp": ..., "message": "Logging configured: ..."} というJSON 形式なので、見た目はJSON でもJSON-RPC メッセージではありません。
原因
元のコードのコメントにも This will be reconfigured in local mode to use stderr(local モードでstderr に再設定される)とあるように、
再設定は後から行われます。しかし、再設定より前 のログ出力はstdout に出てしまいます。
対処
argparse を実行する前に、sys.argv と環境変数 SERVER_MODE からLocal モードかどうかを先に判定し、その結果で最初の setup_logging からstderr を使うようにします。
main.py の差分(git diff の実出力):
--- a/main.py
+++ b/main.py
@@ -17,14 +17,20 @@ if os.path.exists('/app'):
from src.app.config.settings import settings
from src.app.observability.logger import setup_logging, get_logger
-# Setup structured logging for remote mode (default)
-# This will be reconfigured in local mode to use stderr
+# Detect local mode early (before argparse) to avoid polluting stdout with log messages.
+# In local (stdio) mode stdout is reserved exclusively for JSON-RPC messages.
+_is_local_mode = (
+ "--mode" in sys.argv and sys.argv[sys.argv.index("--mode") + 1] == "local"
+ or os.getenv("SERVER_MODE", settings.SERVER_MODE) == "local"
+)
+
+# Setup structured logging — use stderr in local mode to keep stdout clean for MCP protocol
setup_logging(
level=settings.LOG_LEVEL,
service_name=settings.APP_NAME,
json_format=(settings.LOG_FORMAT == "json"),
- log_file=settings.LOG_FILE,
- use_stderr=False, # Remote mode uses stdout
+ log_file=settings.LOG_FILE if not _is_local_mode else None,
+ use_stderr=_is_local_mode,
log_max_bytes=settings.LOG_MAX_BYTES,
log_backup_count=settings.LOG_BACKUP_COUNT,
)
このコードを main.py に反映してください。差分を手で当てる場合は、git apply 用に上記を main.py.patch として保存して適用できます。
git apply --check main.py.patch && git apply main.py.patch
"--mode" in sys.argv and sys.argv[sys.argv.index("--mode") + 1] == "local" は、--mode が引数の末尾にある場合に IndexError になります。
--mode local の形で使う前提の簡易的な判定である点に注意してください。
ハマりどころ ②: /opgrc プレフィックスで認証画面にリダイレクトされる
症状
OpenPages へのリクエストが成功せず、認証が通りません。私の環境では、公式の既定パスである /opgrc/api/v2/... が HTTP 302 で
ログイン画面(logon.jsp)にリダイレクトされました。
原因
公式コードは、標準のOpenPages 向けにREST API のパスを /opgrc/api/v2/... と固定しています(_get_api_path)。
一方、私の環境は OPENPAGES_BASE_URL に /openpages/ が含まれており、REST API は /openpages/api/v2/... で直接公開されていました。
実際に curl で確認した結果です(URL のホスト部は伏せています)。
curl -s -o /dev/null -u "your_username:your_password" \
-w '%{http_code} redirect=%{redirect_url}\n' \
"https://your-openpages-instance.com/openpages/opgrc/api/v2/types"
302 redirect=https://your-openpages-instance.com/openpages/logon.jsp
curl -s -o /dev/null -u "your_username:your_password" \
-w '%{http_code}\n' \
"https://your-openpages-instance.com/openpages/api/v2/types"
200
上記の結果は 私の環境での観測 です。/opgrc が必要なOpenPages 環境(標準構成)も存在します。
お手元の環境で同じ curl を実行し、どちらのパスが200 を返すか確認してから、この修正の要否を判断してください。
対処
API パスのプレフィックスを環境変数 OPENPAGES_API_PREFIX で上書きできるようにします。既定値は従来どおり /opgrc のままなので、
設定しない限り挙動は変わりません。
src/app/config/settings.py の差分:
--- a/src/app/config/settings.py
+++ b/src/app/config/settings.py
@@ -88,6 +88,7 @@ class Settings(BaseSettings):
OPENPAGES_APIKEY_FILE: Optional[str] = None # Path to file containing API key
OPENPAGES_AUTHENTICATION_URL: str = ""
OPENPAGES_INSTANCE_NAME: str = "" # For CP4D deployments
+ OPENPAGES_API_PREFIX: str = "/opgrc" # API path prefix; set to "" if base URL already includes context (e.g. /openpages/)
# Cloud provider the server is provisioned on.
# When set to "aws" the user-presented API key (auth type 4) must be exchanged
src/app/core/openpages_client.py の差分:
--- a/src/app/core/openpages_client.py
+++ b/src/app/core/openpages_client.py
@@ -235,7 +235,12 @@ class OpenPagesClient:
return f"-opgrc{endpoint}"
else:
# Standard OpenPages uses: /opgrc/api/v2/...
- return f"/opgrc{endpoint}"
+ # Allow override via OPENPAGES_API_PREFIX env var (e.g., "" for instances
+ # where the base URL already includes the context path and /api/v2/ is direct)
+ api_prefix = getattr(self.settings, 'OPENPAGES_API_PREFIX', '/opgrc')
+ if api_prefix is None:
+ api_prefix = '/opgrc'
+ return f"{api_prefix}{endpoint}"
この修正は「CP4D 以外(self.is_cp4d が偽)」の分岐にだけ効きます。CP4D 向けの -opgrc の分岐は変更していません。
そして .env に OPENPAGES_API_PREFIX=(空文字)を設定すると、パスは /api/v2/... になります。
手順 4: ローカル起動確認
仮想環境を有効化した状態で、.env を読み込んで起動します。
cd /path/to/OpenPages-MCP/ibm-openpages-mcp-server
source venv/bin/activate
set -a; source .env; set +a
python main.py --mode local
起動すると、サーバーはstdio で入力を待つ状態になり、プロンプトが戻ってこないのが正常 です。
stderr に次のようなログが出ます(私の環境の出力から抜粋)。
2026-10-26 17:22:55,162 - src.app.mcp.local.runner - INFO - Starting local MCP server with stdio transport...
2026-10-26 17:22:55,162 - src.app.mcp.local.stdio_runner - INFO - MCP server v1.0.0 starting in stdio mode...
2026-10-26 17:22:55,162 - src.app.mcp.local.stdio_runner - INFO - Server mode: local
2026-10-26 17:22:55,163 - src.app.mcp.mcp_server - INFO - === Initializing MCP Server ===
Starting local MCP server with stdio transport... が出ていれば、Local モードで起動できています。Ctrl+C で終了します。
stdout が汚れていないことの確認(任意)
ハマりどころ ① の修正が効いているかは、MCP の初期化メッセージを手で流し込み、stdout に JSON-RPC 応答以外の行 が混ざっていないかで確認できます。
ログもJSON 形式で出力されるため、「{ で始まるか」では見分けられません。"jsonrpc" を含まない行を数えます。
set -a; source .env; set +a
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"smoke-test","version":"0.0.1"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"resources/list"}' \
| (cat; sleep 8) | timeout 20 python main.py --mode local >/tmp/stdout.txt 2>/tmp/stderr.txt
grep -vc '"jsonrpc"' /tmp/stdout.txt # JSON-RPC 応答ではない行の数
私の環境では、修正後の main.py で 0 でした。同じ手順を修正前の main.py(git show HEAD:main.py で取り出したもの)で試すと 1 でした。
stdout の内容は、次のようなJSON-RPC 応答のみです(長いため途中を省略しています)。
{"jsonrpc": "2.0", "result": {"protocolVersion": "2025-03-26", "serverInfo": {"name": "local-mcp-server", "version": "1.0.0", ...
timeout コマンドはmacOS には標準で入っていません(brew install coreutils の gtimeout)。ない場合は timeout 20 を外し、数秒待って Ctrl+C で止めてください。
私の検証では、stdin が閉じてもサーバーが自動終了しなかったため、時間で打ち切る必要がありました。
前編のまとめ
前編では次のことを行いました。
- 公式リポジトリをclone し、Python 仮想環境に依存パッケージをインストールした
- Basic 認証用の
.envを作成した - 私の環境で必要だった2 つの修正を入れた
- ①
main.py: Local モードで最初からログをstderr に出す(stdout をJSON-RPC 専用に保つ) - ②
OPENPAGES_API_PREFIX: API パスのプレフィックスを設定で切り替えられるようにする
- ①
-
python main.py --mode localでstdio モードの起動を確認した
この時点で「.env 設定済み・コード修正済み・venv あり」の状態になりました。
後編 では、Bob から子プロセスとして起動するための run_local_bob.sh を作り、.bob/mcp.json に登録して、OpenPages のリソースやクエリがBob から使えることを確認します。あわせて、プロセスが溜まる問題(ハマりどころ ③)も扱います。
▶ 後編: https://qiita.com/mmmmmika/items/98e9008374b4e11fb264
