MCPが便利そうなので Oracle DB とおしゃべりする MCP サーバーを作ってみた（SELECT AI対応：2025/5/3 更新）

## はじめに

最近、MCP(Model Context Protocol)が注目を集めていますね。そこで、MCPを使って、Cursor や Cline などからシームレスに Oracle データベースにアクセスする MCP サーバーを作ってみました。

- こんな感じでテーブルの構造を聞いてみたり

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74534/e9077fb0-3d82-40bd-9651-421b4ac4e8a8.png">

- こんな感じで LLM に SQL を書かせて、その場でテストしてみたりすることができます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74534/06adcf20-019f-485f-8ab1-7d1a12927fef.png">

- こちら↓の Claude Desktop と私のチャットの履歴を見ていただくと、Claude Desktop のような出来の良い MCP Host とデータベースの MCP サーバーがあると何ができるのかイメージし易いかもしれません。

https://claude.ai/share/3de9a321-bdb7-4216-8c67-fecf49073750

- さらに動画でもご紹介

<iframe width="560" height="315" src="https://www.youtube.com/embed/aZ73y4aHA6Y?si=xUqELN0jB4tE2NxD" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

- なんなら経営戦略策定もお願いできます！

https://qiita.com/yuji-arakawa/items/d82607da0c96eb4099c3

- 2025年5月23日（金）に開催された Developer Day 2025でこの MCP サーバーをお供に Oracle の超簡単 RAG ソリューション **"SELECT AI with RAG"** の世界を旅するお話しをさせていただきました。登壇資料はこちらです。

https://speakerdeck.com/kutsushitaneko/hua-ti-no-mcp-toxun-ru-oci-rag-soriyusiyonnolu-select-ai-with-rag-to-generative-ai-agents-deipudaibu

## そもそも、MCPとは？
MCP は、「AIモデルが自由に着脱可能な手足や感覚器官の共通インターフェース規格であり、AIモデルの外界との相互作用を大きく促進する可能性を秘めている」という雰囲気のAIモデルをパワーアップしてくれるプロトコルです。これを作ったのはClaude の Anthropic 社ですので **"Modular Open Nervous-system for Embodied Transformer" ＝ MONET** が良かったのでは？

それはさておき、MCP は、LLMがさまざまなツールやリソースにアクセスするための仕組みです。これにより、LLMはファイルシステム、データベース、APIなどの外部リソースと容易に連携できるようになります。CursorやClaude DesktopなどのMCPホスト（クライアント）は、この仕組みを活用してLLMの能力を拡張して、開発環境としての開発者の体験を向上させることができます。

## MCP のコンポーネント
MCP は以下のような３つのコンポーネントで構成されるクライアント・サーバーアーキテクチャを採用しています。
- ホスト：ユーザーが直接対話するアプリケーション（Claude Desktop、CursorなどのIDE、カスタムエージェント）で、AIモデルが必要に応じてクライアントを通して、サーバーを利用します
- クライアント：ホストアプリケーション内に存在し、特定のMCPサーバーへの接続を管理します
- サーバー：標準APIを通じてツール、リソース、プロンプトを公開する外部プログラムで、プロキシーのような役割を担います

## 作ったもの
今回、作成したのは、MCPサーバーです。 コードは、以下の Github リポジトリで公開してます。

https://github.com/kutsushitaneko/mcp-server-for-oracle-database

:::note
- 更新：
    - 2025/5/3 13:00 以前に clone された方は、pull し直してください
        - SELECT AI / SELECT AI with RAG に対応しました（[動作イメージ](https://claude.ai/share/24ec45a6-d63c-4dd1-928a-c3039abc9ca4)）
    - 2025/4/19 01:40 以前に clone された方は、pull し直してください
        - UNION を使用可能にしました。ただし、UNION ALL は、禁止しています
    - 2025/4/16 16:10 以前に clone された方は、pull し直してください
        - 取得できる最大文字数と最大行数のデフォルトを拡大しました
        - list_tables で、owner を指定して他のスキーマのテーブルをリストできるようになりました
        - describe_table で、owner を指定して他のスキーマのテーブル構造を取得できるようになりました
:::

この「MCP Server for Oracle Database」は、LLMがOracle Databaseに対してSQLクエリを実行できるようにするMCPサーバーの実験的な実装です。ローカル環境で動作し、CursorやClaude DesktopなどのMCPホスト（クライアント）から利用できます。

Cursor などでコード開発をしているときにデータベースの構造を確認したくなったり、SQLをテストしてみたいときにCursorなどのチャットウィンドウを離れることなく Oracle データベースと連携することができます。

### 主な機能

- Oracle DatabaseへのSQLクエリ実行（原則としてSELECT文のみ）
    - SELECT AI / SELECT AI with RAG に対応（2025/5/3）
        - PL/SQL ブロック内の DBMS_CLOUD_AI パッケージのうち安全なプロシージャ/関数の実行に対応
        - EXEC DBMS_CLOUD_AI.SET_PROFILE()に対応
        - ツール起動の度にデータベース接続／クローズを行っていたステートレスな実装をMCPサーバー起動時に接続、終了時にクローズに変更し、ツール起動間でセッションを維持できるようにしました => AI Profile を引数とするDBMS_CLOUD_AI.GENERATE() 関数だけでなく、MCPサーバー動作中は、AI Profile を1度設定すれば SELECT AI action の構文が利用できるようになりました
- テーブル一覧の取得（2025/4/16 Updated）
- テーブル構造の取得
- 若干のセキュリティ対策（クエリ長制限、危険なキーワードチェックなど）
- ~~結果のフォーマット機能（いらないかも）~~
    - mcp.types.TextContent でクエリーの結果とエラーメッセージのみ返すように修正しました。区切り線などの余計な装飾を省いたので返却されるデータ量を減らすことができましたが、代わりにこれまで Claude Desktop が綺麗な表をマークダウンで作ってくれていたものが、「表にして」と指示する必要があるケースが増えました（2025/4/16 Updated）
- ~~MCP のプロンプト機能を使って、LLMに対して tool の使い方をガイドし成功率を挙げている~~
    - 実装が間違っていましたので削除しました。機能していなかったので削除による影響はありません（2025/5/3）

## セットアップ方法

### 必要条件

- Python 3.11以上
- Oracle Databaseへのアクセス権限
- 必要な環境変数の設定（.envファイル）
- uv（高速なPythonパッケージマネージャー）

### インストール手順

1. リポジトリをクローン
    ```bash
    git clone https://github.com/kutsushitaneko/mcp-server-for-oracle-database
    cd mcp-server-for-oracle-database
    ```

2. 仮想環境を作成してアクティベイト
    ```bash
    uv venv .venv
    ```

    - 仮想環境アクティベイト（Linux/Mac/Windows の gitbash などの場合）
        ```bash
        source .venv/bin/activate
        ```

    - 仮想環境アクティベイト（Windows PowerShellの場合）
        ```powershell
        .venv\Scripts\activate
        ```

3. 依存パッケージをインストール
    ```bash
    uv pip install -r requirements.txt
    ```

4. 環境変数の設定
`.env`ファイルを作成し、以下の情報を設定します。

    ```
    ORACLE_USER=your_username
    ORACLE_PASSWORD=your_password
    ORACLE_DSN=your_dsn
    ```

## MCPクライアントへの登録

Claude Desktop や Cursor などの MCPクライアントの設定ファイルに以下を追加します。

```json
{
  "mcpServers": {
    "ORACLE": {
      "command": "仮想環境の Python 実行ファイルへの絶対パス（...\\mcp-server-for-autonomous-database\\.venv\\Scripts\\python.exe）",
      "args": [
        "MCPサーバーの絶対パス（....\\mcp-server-for-oracle-database\\oracledb_mcp_server.py）"
      ]
    }
  }
}
```


※構成ファイルを変更した後は、MCPクライアントの再起動が必要です。Claude Desktopの場合、バックグラウンドで動いているプロセスも一度停止してから再起動してください。

## 提供されるツール

### execute_oracle

SQLクエリ（SELECT文のみ）を実行し、結果をフォーマットして返します。

パラメータ
- query: SQLクエリ（SELECT文のみ）
- params: バインド変数
- max_length: 応答の最大文字数
- max_rows: 取得する最大行数

### list_tables
（2025/4/17 Updated）

テーブルの構造を表示します。

パラメータ
- max_rows: 取得する最大テーブル数（integer型、デフォルト: 50）
- name_pattern: テーブル名のパターン（例: '%EMP%'）（オプション）
- order_by: 並び順（'TABLE_NAME'または'CREATED'、デフォルト: 'TABLE_NAME'）
- include_system_tables: システムテーブルを含めるかどうか（デフォルト: False）
- use_all_tables: ALL_TABLESを参照するかどうか（デフォルト: False）
    - 2025/4/17 Updated
- owner: テーブルの所有者（use_all_tablesがTrueの場合は必須）
    - 2025/4/17 Updated

### describe_table

テーブルの構造を表示します。sqlplusのdescribeを模しています。

パラメータ
- table_name: テーブル名
- owner: テーブルの所有者（オプション）
    - - 2025/4/17 Updated

## 使用例
Claude Desktop を MCPホストとして使用した例をご紹介します。
※ここに挙げるのはあくまでも例です。LLMが文脈に応じて適切にトリガーを引いてくれます（と期待）。

### 利用可能なデータベーステーブルのリストの取得（MCPサーバーが接続しているユーザーが所有するテーブル）

```text
テーブルの一覧を出して
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74534/6b9d95c1-3129-4c09-a30b-2d234dcd813d.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74534/4ad91800-3b60-48eb-91eb-8e6f3b071588.png)


ツールの使用許可を求めて来ます。「このチャットで許可する」か「一度だけ許可」をクリックします。このあたりの表示は、MCPホストによって異なります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74534/94be0cea-01b0-4e02-bf26-75c7c40a5802.png)

デフォルトでは、システムテーブル（テーブル名に$記号が含まれるもの）は除外しています。

「ORACLE（ローカル）からの list_tables の結果を表示」をクリックするとツールからの応答を見ることができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74534/7e3c9a21-570f-4d24-b4e5-a33675453df3.png)

### 他のユーザーが所有するテーブルの一覧を取得する（2025/4/17 Updated）

```text
xxスキーマにはどんなテーブルがありますか？表形式で回答してください
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74534/64abf9f9-07a0-492d-9c7f-443db2cab272.png)

### テーブルの構造を表示

```
xxxx テーブルの構造をしらべて
```

または

```
xxxx のスキーマは
```

![screenshot_sample-01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74534/4f3f2f70-4a42-4f8c-9809-6540f7d34fc3.jpeg)

表形式で表示されないこともあります。その場合は、「images のスキーマを表にして」などと指示します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74534/403bcfaa-23c7-4088-8785-18d222bcc9c0.png)

### テーブルのデータを取得

```
xxxx テーブルのデータを取得して
```

![screenshot_sample-02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74534/89c2f9e6-f28a-4201-b9be-f8a4546b22f8.jpeg)

![screenshot_sample-03.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74534/c1d5e671-2b3a-4acb-af7c-f6a7c834b6e0.jpeg)

![screenshot_sample-04.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74534/cdc8e934-9a15-470a-87a6-cbd1c1b7734f.jpeg)


### 欲しい結果だけ伝えてテーブル名やテーブル構造を調べるのはお任せにするパターン
```text
最近アップした画像20枚のファイル名とアップした日時と説明を表にして
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74534/3622eb57-3f44-4c67-a7e1-3cc7b71f15c9.png)

以下の一連の動作が連続的に実行されています。各ツールの使用を許可するときにだけ人間がインタラクションしています。

- テーブル一覧を取得して画像が保存されていそうなテーブルを特定している
- 特定したテーブルの構造を調べている
-  execute_oracle ツールで、SELECT文を発行してデータの取得を試みる
    - execute_oracle ツールの文字数制限に掛かって結果が途中で打ち切られている
- ツールの文字数制限を緩めるパラメータを追加して、再度、 execute_oracle ツールで、SELECT文を発行している
    - execute_oracle ツールの行数制限に掛かって結果が途中で打ち切られている
- ツールの行数制限を緩めるパラメータを追加して、再度、 execute_oracle ツールで、SELECT文を発行している
    - 再度、文字数制限に掛かっている
- キャプションの一部（先頭100文字）だけを取得するようにSQLを修正して、再度、SELECTしている
- Claude Desktop のアーティファクト機能で一覧表を表示している

ここで、キャプションの先頭100文字（の要約）では心もとないので全文を取得してから要約してもらう

```text
キャプションは先頭100文字ではなく全文を取得して、その要約を表に記載してください。
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74534/56d96b62-53c1-43bd-a014-f7e95f1c547f.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74534/28878b94-3615-4dff-bed3-0934f52d9251.png)

下記の画像のように最大行数と最大文字数のパラメータを拡大して、全文を取得してアーティファクトに表を作り直してくれました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74534/c49a8d34-f69c-4fd3-a4ce-44de8cbfbef8.png)

対話の全体はこちら↓で見ていただくことができます。

https://claude.ai/share/16aaea1c-f0c2-40df-92cf-94312dc769a3

生成されたアーティファクト↓

https://claude.site/artifacts/ccfad1a8-1e85-4277-b2d1-bf3da2ebe3f1


### SQLの実行

```
この SQL を試してみて
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74534/d8325753-f68d-44de-8da2-0caabe47e427.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74534/debec4a0-e7de-4bf5-8164-e948fbdd8c3c.png)

指定したSQLを実行して、エラーとなったので自律的にテーブル構造を確認してくれています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74534/15409549-a5ea-45ed-a759-961cc183c083.png)

SQL を修正して実行してくれました。
ここではツールの文字数制限にかかっていますので、この後も文字数制限値を増やして検索を試みるなど頑張ってくれました。

全体の流れはこちら↓で見ていただくことができます。

https://claude.ai/share/821ba9f5-06ca-4c01-b08e-3c90630f4fa1



## コード解説（2025/4/14 23:20 Updated）
MCP 固有のコードだけ簡単にご紹介します。
### MCPサーバーの初期化
```python
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("ORACLE")
```
`FastMCP`クラスをインポートして「ORACLE」というID/名前でMCPサーバーのインスタンスを作成しています。

### ツール定義
```python
@mcp.tool(
    name = "execute_oracle",
    description = """
    Oracle Databaseに対してSQLクエリを実行し、結果をフォーマットして返す。
        Args:
            query: 実行するSQLクエリ（必須）
            params: バインド変数に使用するパラメータ（辞書型　例：{"parameter1": 5}）
            max_length: 応答の最大文字数（integer型、デフォルト: 1000）
            max_rows: 取得する最大行数（integer型、デフォルト: 10）
        ヒント:
            文字数制限にかかったときは、max_lengthを大きくしてください。
            行数制限にかかったときは、max_rowsを大きくしてください。
            結果をマークダウンで表示する場合には、テーブル名に含まれる$記号記号が特殊文字として扱われるため、バックスラッシュでエスケープすることを忘れないでください。
    """)
def execute_oracle(query: str, params: dict = None, max_length: int = 1000, max_rows: int = 10) -> str:
    # 以下は処理の実装
```
`@mcp.tool`デコレータは、MCPサーバーにツールとして公開する関数を定義しています。この例では「`execute_oracle`」という名前のツールが定義されており、引数とその説明が提供されています。

### サーバー起動
```python
if __name__ == "__main__":
    # stdioで通信
    mcp.run(transport="stdio")
```
これがMCPサーバーを実際に起動する部分です。標準入出力（stdio）を使用して通信するように設定されています。これにより、このPythonスクリプトを実行すると、標準入出力を通じてMCPプロトコルでクライアントと通信するサーバーとして動作します。

## セキュリティについて

本プロジェクトでは、最小限のセキュリティ対策を施しています。

- SELECT文以外は受け付けません
- クエリ長の制限（デフォルト: 1MB）
- 危険なキーワードのチェック
- 入力値のサニタイズ
- 読み取り専用クエリの検証

⚠️ **注意**: 外部からアクセスできないローカルな環境でのみ使用してください。

## おわりに

2,3 時間でざっと作ったものなのでまだまだ荒いところが沢山ありますが、この段階でも便利さを感じています。
MCP恐るべしですね。

**※本記事で紹介したプロジェクトは実験的なものであり、本番環境での使用は推奨していません。**
**※サーバーなのでデータベースコネクションはもっと賢く管理したいところですが現在のコードは MCP クライアントからリクエストが来るためにコネクションを確立して、応答完了ごとに閉じています。改善したいですね。**