【前編】IBM OpenPages の MCP サーバーを IBM Bob に接続する ― 環境構築からローカル起動確認まで
【前編】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 パスのプレフィックス)と、その修正差分
全体構成
flowchart LR
Bob[IBM Bob] -- stdio / JSON-RPC --> Wrapper[run_local_bob.sh]
Wrapper -- exec --> Server["main.py --mode local"]
Server -- "REST API / Basic 認証" --> OP[IBM OpenPages]
前編では、右側の main.py --mode local とOpenPages の接続までを作ります。run_local_bob.sh と Bob 側の登録は後編です。
:::note warn
公式リポジトリに 手を加える 手順を含みます(手順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 |
:::note info
requirements.txt は mcp>=1.9.4 のように 下限のみの指定 なので、インストール時点の最新版が入ります。
バージョンを固定したい場合は、動作確認済みの pip freeze の結果を残しておくと再現しやすくなります。
:::
:::note warn
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
!op1.png
主要な依存パッケージとバージョンは、冒頭の「前提条件」の表のとおりです。
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 で追加します |
:::note alert
.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
:::note info
"--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
:::note warn
上記の結果は 私の環境での観測 です。/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", ...
:::note info
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 から使えることを確認します。あわせて、プロセスが溜まる問題(ハマりどころ ③)も扱います。
▶ 後編: qiita.com
参考
- IBM OpenPages(IBM 製品ページ)
- IBM Bob(公式サイト)
- Model Context Protocol(公式サイト)
- IBM/ibm-openpages-mcp-server(GitHub)
1 like
Mentions
【前編】IBM OpenPages の MCP サーバーを IBM Bob に接続する ― 環境構築からローカル起動確認まで 【前編】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 パスのプレフィックス)と、その修正差分 全体構成 flowchart LR Bob[IBM Bob] -- stdio / JSON-RPC --> Wrapper[run_local_bob.sh] Wrapper -- exec --> Server["main.py --mode local"] Server -- "REST API / Basic 認証" --> OP[IBM OpenPages] 前編では、右側の main.py --mode local とOpenPages の接続までを作ります。run_local_bob.sh と Bob 側の登録は後編です。 :::note warn 公式リポジトリに 手を加える 手順を含みます(手順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 | :::note info requirements.txt は mcp>=1.9.4 のように 下限のみの指定 なので、インストール時点の最新版が入ります。 バージョンを固定したい場合は、動作確認済みの pip freeze の結果を残しておくと再現しやすくなります。 ::: :::note warn 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 !op1.png 主要な依存パッケージとバージョンは、冒頭の「前提条件」の表のとおりです。 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 で追加します | :::note alert .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 :::note info "--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 :::note warn 上記の結果は 私の環境での観測 です。/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", ... :::note info 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 から使えることを確認します。あわせて、プロセスが溜まる問題(ハマりどころ ③)も扱います。 ▶ 後編: qiita.com 参考 - IBM OpenPages(IBM 製品ページ) - IBM Bob(公式サイト) - Model Context Protocol(公式サイト) - IBM/ibm-openpages-mcp-server(GitHub)