【後編】IBM OpenPages の MCP サーバーを IBM Bob に接続する ― Bob への登録から疎通確認・ハマりどころ総括まで
はじめに
前編 では、IBM 公式の IBM/ibm-openpages-mcp-server をclone し、python main.py --mode local でMCP サーバーをstdio モードで起動できるところまで進めました。
後編では、これを IBM Bob から子プロセスとして起動させ、OpenPages のデータをBob から扱える状態 にします。
| 記事 | 内容 |
|---|---|
| 前編 | 環境構築 → .env 設定 → コード修正 → ローカル起動確認 |
| 後編(本記事) | 起動ラッパー作成 → Bob への登録 → 疎通確認 → ハマりどころ総括 |
検証環境(要約)
詳細は 前編の「前提条件」 にまとめています。後編の内容に関わる主なバージョンは次のとおりです。
| 項目 | バージョン |
|---|---|
| IBM OpenPages | 9.2.1 |
| IBM Bob | 2.2.3(macOS 版) |
| OS / Python | macOS 26.6.2(arm64) / Python 3.13.1 |
mcp(Python SDK) |
2.3.0 |
Bob のMCP 設定や画面の表記、公開されるツールの一覧は、Bob のバージョンによって変わる可能性があります。
前編の到達点(この記事の前提)
後編は、次の状態になっていることを前提にします。
-
ibm-openpages-mcp-server/にvenv/があり、pip install -r requirements.txt済み -
.envが作成済み(OPENPAGES_BASE_URL/OPENPAGES_USERNAME/OPENPAGES_PASSWORDなど) -
前編の手順3 の修正(
main.pyの stdout 汚染対策、OPENPAGES_API_PREFIXの追加)が反映済み -
python main.py --mode localが起動する
全体構成
本記事の表記ルールは前編と同じです。https://your-openpages-instance.com・your_username・your_password・/path/to/OpenPages-MCP はダミーです。
手順 5: 起動ラッパー run_local_bob.sh の作成
なぜラッパーが必要か
Bob はMCP サーバーを 子プロセスとして直接起動 します。前編では set -a; source .env; set +a で .env を手動で読み込んでから起動しましたが、
Bob の設定にそれを書くのは面倒です。そこで「.env を読み込んでから main.py を起動する」ラッパースクリプトを1 本用意しました。
(.bob/mcp.json の env に環境変数を直接書く方法もありますが、パスワードを設定ファイルに書くことになるため、私は .env に集約する方式にしました。)
スクリプト全文
ibm-openpages-mcp-server/run_local_bob.sh として保存します。
#!/bin/bash
# Bob の MCP サーバー起動スクリプト
# .env を読み込んでから main.py をローカルモードで起動する
# Bob は stdio 経由でこのスクリプトを子プロセスとして起動する
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
# 既存の main.py --mode local プロセスを終了させる(プロセス蓄積防止)
# 複数タスクが起動するたびに古いプロセスが残存するのを防ぐ
pkill -f "main.py --mode local" 2>/dev/null || true
sleep 0.3
# .env ファイルが存在する場合に読み込む(既に環境変数が設定されている場合は上書きしない)
ENV_FILE="$SCRIPT_DIR/.env"
if [ -f "$ENV_FILE" ]; then
# export しつつ読み込む(コメント行・空行はスキップ)
set -a
# shellcheck source=/dev/null
source "$ENV_FILE"
set +a
fi
# venv の Python で main.py をローカルモードで実行
exec "$SCRIPT_DIR/venv/bin/python" "$SCRIPT_DIR/main.py" --mode local
実行権限を付けます。
chmod +x /path/to/OpenPages-MCP/ibm-openpages-mcp-server/run_local_bob.sh
スクリプトがやっていることは次の4 つです。
- スクリプト自身の置き場所(
ibm-openpages-mcp-server/)をSCRIPT_DIRとして求める - 既存の
main.py --mode localプロセスを終了する(ハマりどころ ③。後述) -
.envをsourceして環境変数としてexport する -
execでvenv のPython に置き換わり、main.py --mode localを起動する
exec を使うので、シェルのプロセスは残らず、Bob はPython プロセスと直接stdio でやり取りします。
Python は main.py のあるディレクトリをimport パスに加えるため、from src.app... のimport も解決されます。
この記事の .env の読み込みは、単純な KEY=value 形式を前提にしています。値に空白や $ などのシェルの特殊文字を含む場合は、引用符で囲むなど、source で正しく解釈される形にしてください。
ハマりどころ ③: Bob のタスクごとにプロセスが溜まる
症状
Bob は新しいタスクを開くたびにMCP サーバーのプロセスを起動します。私の環境では、古いプロセスが終了されないまま残り、
main.py --mode local が 複数並走 することがありました。蓄積するとCPU 使用率が上がり、OpenPages への接続でDNS 解決失敗([Errno 8])が出ました。
対処
ラッパーの先頭で、起動前に古いプロセスを終了させます(上のスクリプトの pkill と sleep 0.3)。
pkill -f "main.py --mode local" 2>/dev/null || true
sleep 0.3
蓄積が起きてしまった場合は、手動でも同じコマンドで掃除できます。
pgrep -a -f "main.py --mode local" # 稼働中のプロセスを確認
pkill -f "main.py --mode local" # まとめて終了
検証結果
pkill が効くかを、ラッパーを手動で3 回続けて起動して確認しました。
(起動前にBob 経由のプロセスが1 つ動いている状態から開始しました。)
for i in 1 2 3; do
( sleep 12 | ./run_local_bob.sh >/dev/null 2>&1 & )
sleep 3
echo "after launch $i: $(pgrep -f '[m]ain.py --mode local' | wc -l) process(es)"
done
| タイミング |
main.py --mode local のプロセス数 |
|---|---|
| 起動前(Bob 経由の 1 つが稼働中) | 1 |
| 1 回目の起動後 | 1 |
| 2 回目の起動後 | 1 |
| 3 回目の起動後 | 1 |
起動するたびに古いプロセスが終了し、プロセスが 1 つのまま増えない ことを確認できました。
確認できたこと・できていないこと
- 確認できた: ラッパーを手動で連続起動しても、プロセスが蓄積しない
- 確認できていない: Bob で複数のタスクを実際に開閉したときの挙動。上の検証は、ラッパーを手動で繰り返し起動する形で再現したものです
副作用に注意
pkill -f "main.py --mode local" は、コマンドラインに main.py --mode local を含むプロセスをすべて 終了します。
そのため次のような副作用があります。
- 検証の際、動作中だったBob 用のプロセスも終了しました。その後Bob からクエリを実行すると、新しいプロセスが立ち上がって結果が返りました
- Bob のウィンドウを複数開いて同じMCP サーバーを使っていると、片方の起動がもう片方のサーバーを終了させます
- 無関係な同名のプロセスがあれば、それも終了します
複数ウィンドウで同時に使う場合や、他の用途で main.py --mode local を動かす場合は、この方式は向きません。
その場合はPID ファイルで管理するなど、終了対象を絞る方法を検討してください。
手順 6: .bob/mcp.json への登録
Bob のMCP 設定は2 階層あります(Bob 公式ドキュメントによる)。
| 範囲 | 場所 | 用途 |
|---|---|---|
| グローバル | ~/.bob/mcp.json |
すべてのワークスペースで有効 |
| プロジェクト | <プロジェクトルート>/.bob/mcp.json |
そのプロジェクトのみ。同名のサーバーはプロジェクト側が優先される |
今回はグローバルに設定をします。
-
json に次の内容を登録します
{ "mcpServers": { "openpages-mcp": { "command": "/path/to/OpenPages-MCP/ibm-openpages-mcp-server/run_local_bob.sh", "args": [], "env": {} } } }
-
command: STDIO トランスポートでは必須。実行するファイルを指定します。ここではラッパーの絶対パスを書きました -
args: 引数の配列。ラッパー側で--mode localを付けているので空です -
env: 環境変数。.envはラッパーが読むため空です
/path/to/... の部分は、お使いの環境の絶対パスに置き換えてください。保存すると、Bob のMCP パネルにサーバーが表示されます。
私の環境では、接続状態が Connected になりました。
手順 7: Bob からの疎通確認
以降は、Bob のチャットからOpenPages MCP のツールを使った結果です。数値は私のインスタンスのものです。
7-1. Bob からのリソース一覧の取得
Bob のチャットから自然言語で実行してみます。
openpages-mcpを使って、リソース一覧を取得して
Bob は list_resources を呼び出しました。このツールには引数が不要で、クエリの組み立ても発生しません。

取得結果(抜粋)
Bob は、この結果を次のように整理して返してくれました。
スキーマはさらに、SOX / 内部統制(SOXRisk、SOXControl、SOXIssue など)、オペレーショナルリスク、コンプライアンス・規制、監査、事業継続、モデルリスク、サードパーティ管理といった分野別に分類されて表示されました。
一覧に出てきた URI は、get_resource にそのまま渡すことで中身を取得できます。クエリを組み立てる前にスキーマを確認する流れの出発点になります。
7-2. Bob からのクエリ実行例(SOXIssue の取得)
Bob のチャットから自然言語で次のように指示を出します。
openpages-mcpを使って、OpenPagesのSOXIssueを5件取得して
指示を受けた Bob は、以下のステップを自律的に行いました。
-
スキーマの取得 (
get_resource)
クエリ構築前にopenpages://schema/SOXIssue(compact モード)を参照し、SOXIssueの正確なフィールド名([SOXIssue].[Resource ID],[SOXIssue].[OPSS-Iss:Status]などプレフィックス付きフィールドを含む)を確認

実行されたクエリ
SELECT
[SOXIssue].[Resource ID],
[SOXIssue].[Name],
[SOXIssue].[Description],
[SOXIssue].[OPSS-Iss:Status],
[SOXIssue].[OPSS-Iss:Priority],
[SOXIssue].[OPSS-Iss:Due Date]
FROM [SOXIssue]
取得結果
OpenPages 上の Issue データ(ID、名前、ステータス、優先度、期日、説明)が正常に 5 件取得され、Bob の画面上にフォーマットされて返ってきました。
OpenPages のクエリ言語には、フィールド名を [オブジェクト型].[フィールド名] の形式で完全修飾する、AS によるエイリアスは使えない、といったルールがあります。
フィールド名はインスタンスの構成ごとに異なるため、MCP サーバーが提供する openpages://schema/<型名> で事前にフィールド定義を取得して組み立てる必要があります。Bob はこの仕様を理解し、自動的にスキーマを取得してからクエリを実行します。
7-3. REST API へのアクセスの確認(任意)
Bob を介さずに、OpenPages のREST API へ直接アクセスできているかは、前編で使った curl でも確認できます。
curl -s -o /dev/null -u "your_username:your_password" \
-w '%{http_code}\n' \
"https://your-openpages-instance.com/openpages/api/v2/types"
200
ハマりどころのまとめ
前編・後編を通して遭遇した 3 点を一覧にします。
| # | 症状 | 原因 | 対処 | 該当 |
|---|---|---|---|---|
| ① | Local(stdio)モードで、stdout にJSON-RPC 以外のログ行が混ざる |
main.py が最初の setup_logging で use_stderr=False を使い、stderr への切り替えが argparse の後だった |
argparse の前に --mode / SERVER_MODE からLocal モードを判定し、最初から use_stderr=True にする |
前編 手順3 |
| ② |
/opgrc/api/v2/... がHTTP 302 で logon.jsp にリダイレクトされ、認証が通らない(私の環境) |
API パスが /opgrc に固定されていた。私の環境は /openpages/api/v2/... が直接のREST API だった |
OPENPAGES_API_PREFIX を追加し、.env で空文字を設定する |
前編 手順3 |
| ③ | Bob のタスクごとに main.py --mode local が溜まり、CPU 使用率の上昇とDNS 解決失敗 [Errno 8] が出た |
古いプロセスが終了されずに残る |
run_local_bob.sh の先頭で pkill する |
後編 手順5 |
注意事項
-
stdout はJSON-RPC 専用です。Local モードで
print()などをstdout に出力すると、Bob との通信が壊れます。ログや調査用の出力はstderr に出してください -
認証情報の管理:
.envは.gitignoreに含め、コミットやログ、記事・スクリーンショットに値を出さないでください -
通信の暗号化: 私が使ったインスタンスはHTTP(非SSL)でBasic 認証が使えましたが、これは検証用途の構成です。本番環境ではHTTPS を使い、
SSL_VERIFY=trueにしてください -
ラッパーの
pkillの副作用: 手順5 の「副作用に注意」を確認してください -
公式リポジトリへの修正: 今回の修正は、公式リポジトリへの変更として取り込まれたものではなく、私の環境で入れたローカルの変更です。公式リポジトリを更新(
git pull)する際は、競合に注意してください
おわりに
2 本の記事で、IBM OpenPages のMCP サーバーをIBM Bob にLocal(stdio)モードで接続し、Bob からOpenPages のリソース取得とクエリ実行ができるところまで確認しました。
今回は次の点を扱っていません。公式README には記載があるので、必要に応じて参照してください。
- Remote(HTTP)モード(
streamable-http)での接続。複数ユーザーでの共有に向きます - Bearer 認証(IAM / MCSP / CP4D)
- Docker での実行、監視スタック
公式のREADME では、Local モードは「IBM Bob のようなMCP クライアントから、1 ユーザーで使う場合」の用途として説明されています。
▶ 前編: https://qiita.com/mmmmmika/items/fc67a362df2c928f0e49
参考
- IBM/ibm-openpages-mcp-server(GitHub)
- IBM Bob(公式サイト)
- IBM Bob 公式ドキュメント「Using MCP in Bob」





