1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Oracle DatabaseをMCPサーバ+LangGraph ReAct Agentを用いて自然言語で操作してみる

Last updated at Posted at 2025-07-31

はじめに

先日Oracle DatabaseがMCPサーバに対応しました。
MCPサーバリリース

Oracle Database MCP Serverを使用するメリットは沢山ありますが主に以下になるかと思います。

  1. AI/LLM からの自然言語アクセスが可能に
    Model Context Protocol(MCP)サーバーを介して、AI エージェントや大規模言語モデル(LLM)が SQLcl 経由で Oracle Database に直接「自然言語」で問い合わせ・操作できます。。従来はアプリケーションやカスタム“つなぎ”コードが必要だった部分を、標準化されたプロトコルでシンプルに実現可能です。

  2. SELECT だけでなく DML(INSERT/UPDATE/DELETE)対応
    SQLcl の MCP 拡張は読み取り系だけでなく、書き込み系(INSERT、UPDATE、DELETE)もサポート。AI エージェントがメタデータを自動調査したうえで安全に更新処理を実行できることで、より完成度の高い自律的 DB 操作が可能になります。

  3. 既存の認証・セキュリティ機構をそのまま活用
    MCP サーバーは Oracle Database の既存接続情報やユーザ権限、監査ログなどをそのまま利用。AI 経由の操作も従来と同じ最小権限・監査記録の対象となるため、ガバナンス要件を満たしつつ新しいワークフローを導入できます 。

  4. 開発者/DBA の生産性向上

    • メタデータ自動探索:テーブル一覧や列情報を自動で問い合わせることで、AI がスキーマ構造を理解し、推測なしに正確なクエリを組み立てる。
    • ツール連携の手軽さ:SQLcl をホストするだけで MCP サーバーが起動し、VS Code の SQL Developer 拡張や Copilot など既存の IDE/AI アシスタントとワンクリックで統合できます。
  5. 導入の敷居が低い
    対応は Oracle SQLcl(バージョン 25.2 以上)と Java 17 以上の環境があれば完了。オンプレミスでもクラウド(OCI/AWS/Azure/GCP)でも同様に動作し、手持ちのデータベースをすぐ AI ネイティブなインターフェースに変換できます。

今回こちらの構成でBaseDBまたはADBにおいて、MCPを用いて自然言語で操作してみたいと思います。

image.png

VMにMCPサーバに必要なSQLclとJavaをインストールします。
SQLclはADBだけでなくBaseDBも扱えるため、両方で試してみます。

DB構築

OCIコンソール等から立ち上げ、必要なユーザを作成しておきます。
今回の検証用に以下の簡単なテーブルと数レコードをinsertしておきます。

今回使うテーブルたち
+----------------------+        +--------------------------------+
|     departments      |<-------|           employees            |
+----------------------+        +--------------------------------+
| PK  department_id    |        | PK   employee_id               |
|     department_name  |        |      first_name                |
|                      |        |      last_name                 |
|                      |        | FK   department_id             |
|                      |        |      salary_amount             |
+----------------------+        +--------------------------------+

VM構築

新規もしくは既存のVMに関して下記をインストールしていきます。

Oracle SQLcl MCP Server

上記サイトよりOracle Database MCPサーバを扱うためには下記が必要:

Oracle SQLcl, version 25.2.0 or higher
Java Runtime Environment (JRE), version 17 or higher

SQLcl

SQLcl DLサイト

リンクに対してwget等でDL後、unzipで解凍します。
※執筆時点2025/7/31の最新は25.2.2

Java

JavaDLサイト
通常のOLであればx64 Compressed Archiveを選択しDL

tar zxvf jdk-24_linux-x64_bin.tar.gz 

~/.bashrcの最後尾に下記を追記しておきましょう。

export JAVA_HOME=/home/opc/jdk-24.0.2
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=.:$JAVA_HOME/lib:$JAVA_HOME/lib/tools.jar

MCP接続情報作成例

BaseDBの接続情報をもとに接続情報を作成して保存しておきます。
このmcptestという接続名はMCPクライアントから後ほど使用します。

basedbのMCP接続作成
/home/opc/sqlcl/bin/sql -save mcptest -savepwd (usr)/(pwd)@10.0.1.74:1521/DB0729_pdb1.sub09140458561.vcn.oraclevcn.com

ADBの場合は同様にウォレットを配置して設定します。

adbのMCP接続作成
/home/opc/sqlcl/bin/sql -cloudconfig "/path/to/Wallet.zip" -save mcptest -savepwd (usr)/(pwd)@sez0vdu90uaghse2_tp

MCPクラサバ構築

Pythonでアプリを作成します。

pip install oci langchain_mcp_adapters langgraph langchain-community
MCPクラサバ対応アプリ
import asyncio
import oci
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from langchain_community.chat_models import ChatOCIGenAI
from langchain_mcp_adapters.tools import load_mcp_tools
from langgraph.prebuilt import create_react_agent

config = oci.config.from_file("~/.oci/config", "DEFAULT")

model = ChatOCIGenAI(
    # model_id="cohere.command-plus-latest",
    model_id="cohere.command-a-03-2025",
    service_endpoint="https://inference.generativeai.ap-osaka-1.oci.oraclecloud.com",
    compartment_id="ocid1.compartment.oc1...", #compartment指定
    model_kwargs={"temperature": 0.7, "max_tokens": 4000},
)
server_params = StdioServerParameters(
    command="/home/opc/sqlcl/bin/sql", #sqlclのパス指定
    args=["-mcp"],
    transport="stdio"
)
prompt = '''
あなたは Oracle Database を操作する丁寧なアシスタントです。  
すべてのやり取りは **ReAct 形式**  
Thought → Action → Action Input → Observation … → Final Answer  
で行い、Thought では必ず「次の方針」を自問・宣言してください。

────────────────────────────────────
# 0. 接続フェーズ
- セッション開始時、**必ず最初に** `connect` ツールを呼び出し、接続名 **mcptest** で接続すること。
- 接続前に SQL を実行してはいけない。

# 1. メタデータ確認フェーズ
- ユーザ質問を読んだら「答えに必要な表名・列名・結合条件をすべて正確に把握しているか」を自問する。
- 一つでも不確実な点があれば **必ず** 先にメタデータを調べる。推測で列名や表名を使わない。
- 代表的な調査 SQL 例(状況に合わせて自由に組み立ててよい)  
* **テーブル一覧取得**  
    ```sql
    SELECT table_name
    FROM   user_tables;
    ```
* **列名取得**  
    ```sql
    SELECT column_name
    FROM   user_tab_columns
    WHERE  table_name = '<TABLE_NAME>';
    ```
* **テーブル概要確認**  
    ```sql
    DESCRIBE <TABLE_NAME>;
    ```
- **テーブル名が推測できない場合は列名から逆引き**して表を特定すること。  
1. 必要列名(例: SALARY, PAY, COMPENSATION など)を `all_tab_columns` で LIKE 検索し、候補テーブルを列挙。  
    ```sql
    SELECT DISTINCT table_name
    FROM   all_tab_columns
    WHERE  UPPER(column_name) LIKE '%SALAR%';  -- 必要に応じて '%PAY%' 等を追加
    ```
2. 得た table_name を `all_tables` で再確認し、存在を確定。  
    単数形/複数形ゆらぎや別スキーマ・シノニムも視野に入れる。  
    ```sql
    SELECT owner, table_name
    FROM   all_tables
    WHERE  UPPER(table_name) LIKE '%SALAR%';
    ```
3. 必要に応じて `DESCRIBE` で列詳細を確認してから本クエリへ進む。

# 2. 本クエリ実行フェーズ
- 必要事項を確定したら、解答に直結する単一の SQL を `run_sql` で実行する。
- コーディング規約  
* 短いテーブル別名を付け、すべて `<別名>.<列名>` で参照。  
* 不要な列は選択しない。  
* 1 件のみで良いと判断できる場合は `FETCH FIRST 1 ROW ONLY` を付与。  
* 複数表を扱う場合は結合条件を必ず指定。  

# 3. 回答フェーズ
- Observation で得た結果を日本語で端的にまとめ、**Final Answer:** で始める。
- 調査用 SQL の実行ログや長大な Observation は引用しない。要点のみ反映する。

# 4. 禁則
- `connect` を忘れた状態で SQL を発行しない。  
- メタデータ確認を省略して推測列を使わない。  
- 不要なテーブルや列を開示しない(ユーザが求めた情報の範囲内で回答する)。  
- ユーザが再質問した場合も、同様に 0 → 3 の手順を厳守する。
────────────────────────────────────
'''

async def main(question: str):
    """
    メイン処理。引数 `question` をユーザの問い合わせとして渡します。
    """
    async with stdio_client(server_params) as (reader, writer):
        async with ClientSession(reader, writer) as session:
            await session.initialize()
            tools = await load_mcp_tools(session)
            agent = create_react_agent(model=model, tools=tools, prompt=prompt)
            async for chunk in agent.astream(
                {"messages": [{"role": "user", "content": question}]},
                stream_mode="updates"
            ):
                if isinstance(chunk, dict) and "agent" in chunk:
                    for msg in chunk["agent"]["messages"]:
                        print(msg.content)
                        print('-' * 40)

if __name__ == "__main__":
    question = "全従業員の名前一覧とその人の所属および給料を教えて"
    # question = "Johnの給料を70000にして"
    asyncio.run(main(question))

まずOCI GeneAIサービスとしてCommand Aを使用します。
続いてStdioServerParametersの部分でSQLclを使ってMCPサーバをstdioのローカルサーバとして起動します。
その後プロンプトではAIエージェント用のReActと呼ばれる推論のフレームワークを用い、DBのテーブル関連メタ情報を渡さずとも、現状のデータ構造を探しに行って処理させるようにします。
またプロンプト内では先ほど作成したmcptestという接続名でまず接続させるよう指示しています。

今回はこういったプロンプトですが、事前にテーブル情報が明確に固定であればテーブル情報をプロンプト等に埋め込み、事前情報として与えた方が良いかもしれません。

動作確認🤖

自然言語でSelectしてみる

上記アプリを実行します。
まず下記のように自然言語で問い合わせします。

question = "全従業員の名前一覧とその人の所属および給料を教えて"

全従業員の名前一覧とその人の所属および給料を教えて
まず、mcptestで接続します。その後、従業員の名前、所属、給料に関する情報を含むテーブルと列を特定するために、メタデータを確認します。
----------------------------------------
接続が確認できました。次に、従業員の名前、所属、給料に関する情報を含むテーブルと列を特定するために、メタデータを確認します。
----------------------------------------
テーブル一覧から、従業員に関する情報はEMPLOYEESテーブルに含まれる可能性が高いです。次に、EMPLOYEESテーブルの列名を確認します。
----------------------------------------
EMPLOYEESテーブルには、従業員の名前(FIRST_NAME、LAST_NAME)、所属(DEPARTMENT_ID)、給料(SALARY_AMOUNT)に関する列が存在することが確認できました。次に、DEPARTMENT_IDがどのテーブルに関連しているかを確認します。
----------------------------------------
DEPARTMENT_IDは複数のテーブルに存在することがわかりました。次に、DEPARTMENT_NAMEを含むテーブルを確認します。
----------------------------------------
DEPARTMENT_NAMEはDEPARTMENTSテーブルに存在する가ことがわかりました。次に、従業員の名前、所属、給料に関する情報を取得するために、EMPLOYEESテーブルとDEPARTMENTSテーブルを結合してクエリを実行します。
----------------------------------------
Final Answer: 全従業員の名前、所属、給料は以下の通りです。

- John Doe、Engineering、60,000
- Alice Johnson、Engineering、80,000
- Jane Smith、Marketing、30,000
----------------------------------------

上記が実行時のログです。
エージェントのログを見ると、テーブル構造を順番に把握していって理解した後に上手くjoinして結果を出力しています。

自然言語でUpdateしてみる

続いて、Johnが頑張ったので給料を少し増やします😊

question = "Johnの給料を70000にして"

Johnの給料を70000にして
`mcptest` への接続が確立されました。次に、給与情報が格納されているテーブル名と、John のレコードを特定するための列名、および給与額の列名を特定するためにメタデータを取得します。

まず、給与に関連するテーブルを特定するために、`all_tab_columns` から `SALARY`、`PAY`、`COMPENSATION` を含むテーブルを検索します。
----------------------------------------
`all_tab_columns` から `SALARY`、`PAY`、`COMPENSATION` を含むテーブルを検索した結果、`EMPLOYEES` と `USER_SUBSCR_REGISTRATIONS` の 2 つのテーブルが候補として見つかりました。 次に、これらのテーブルが実際に存在するかどうかを `all_tables` で確認します。
----------------------------------------
`all_tables` で確認した結果、`EMPLOYEES` テーブルが存在することを確認しました。次に、`EMPLOYEES` テーブルの詳細を `DESCRIBE` で確認し、John のレコードを特定するための列名と給与額の列名を特定します。
----------------------------------------
`EMPLOYEES` テーブルの詳細を確認した結果、`FIRST_NAME` 列と `SALARY_AMOUNT` 列が存在することを確認しました。次に、John のレコードを特定し、給与額を 70000 に更新する SQL クエリを実行します。
----------------------------------------
Final Answer:
John の給与を 70000 に更新しました。
----------------------------------------
[opc@mcptest mcp_basedb]$ 

おわりに

今回はOracle Base Databaseに対して、MCPサーバ・クライアントつきのアプリを作り、自然言語でDB操作を実行してみました。
Oralce公式のMCPサーバのツールのみ使用でしたが、LangGraphのPrebuilt ReAct AgentとOCI GenAIサービスのLLMを用いて自律的にDB操作を実行するエージェントの確認ができました。

適切な権限を持ったユーザを用いて、自然言語でDBを操作をさせることで、システム側で作りこまずにユーザ側でデータ検索、更新ができることになり、様々なSaaSで有効活用できるのではと思います。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?