Deprecated: The each() function is deprecated. This message will be suppressed on further calls in /home/zhenxiangba/zhenxiangba.com/public_html/phproxy-improved-master/index.php on line 456
Claude × MCPで社内DBに話しかけてみた #TypeScript - Qiita
[go: Go Back, main page]

58
46

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Claude × MCPで社内DBに話しかけてみた

Posted at

はじめに

本記事で紹介する Model Context Protocol (MCP) サーバーは、クラウドや社内ネットワーク上のリモートサーバーではなく、ローカル PC 上で動作するローカルサーバーです。
最終的に Claude Desktop との通信も標準入出力(stdio)経由で行います。

この記事でやること(先に概要)

  1. MCP サーバー(TypeScript)を用意
    1. MCP サーバーを作成
    2. dotenv 経由で環境変数を読み込む
    3. Tools として SELECT クエリ専用の読み取り API を公開
    4. SELECT 専用の安全実行
    5. エラーや実行 SQL をログファイルに出力
  2. MCP Inspector を使って動作確認
  3. Claude Desktop に mcp 設定を追加し、「自然言語 → SQL」の対話フローを実現

ℹ️ 補足:本記事では DB (Azure SQL Database) への接続設定方法については扱いません。接続情報はご自身の環境に合わせて設定してください。

🧪 成果:Claude に「直近のアクセスログを教えて」と話しかけると、MCP サーバーが DB に安全にアクセスし、サマリーを返してくれる。

背景とねらい

  • ユーザーは自然言語で、DB に保管されている社内情報へアクセスしたい。
  • 従来は、 LangChain などのフレームワークを用いて 自然言語 → SQL 変換処理を自前で組み上げる必要があった。
  • MCP を利用することで、ツール連携や入出力のやり取りを標準化でき、自然言語から SQL への変換を LLM 側に委ねる設計を取りやすくなる。
    その結果、専用のオーケストレーション層をゼロから構築する工数を大幅に削減できる。
  • まずはローカル環境で MCP サーバーを構築し、期待通り動作するか検証する。

全体アーキテクチャ

Claude Desktop ──(MCP stdio)──> Node.js MCP Server ──> Azure SQL Database
       ▲                                            ▲
       │                                            │
  自然言語プロンプト                          Entra ID 認証
  • 通信は stdio を使用
  • DB 接続には Entra ID 認証を使用

事前準備

1) 環境変数(例)

ルートディレクトリに .env を作成し、接続先の DB 情報などを記載します。

# DB 接続
DB_SERVER=your-sql-server.database.windows.net
DB_PORT=1433
DB_NAME=your_db

# 認証
AUTH_TYPE=azure-active-directory-default
AZURE_TENANT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

2) パッケージ

必要な npm パッケージをインストールします。

npm i @modelcontextprotocol/sdk mssql dotenv @azure/identity

実装ハイライト

MCP サーバーを作成

以下のコードは、ローカルで MCP サーバーを実行するための最小構成です。
ここでは src/index.ts に記載している想定です。

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
    CallToolRequestSchema,
    ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import dotenv from "dotenv";
import { dirname, resolve } from "path";
import { fileURLToPath } from "url";

const server = new Server(
    {
        name: "xxx-server",
        version: "1.0.0",
    },
    {
        capabilities: {
            tools: {},
        },
    }
);

// ツール一覧
server.setRequestHandler(ListToolsRequestSchema, async () => {
    // 後述
});

// ツール実行
server.setRequestHandler(CallToolRequestSchema, async (request) => {
    // 後述
});

// サーバー起動
async function main() {
    const transport = new StdioServerTransport();
    await server.connect(transport);
}

main().catch(() => process.exit(1));

dotenv 経由で環境変数を読み込む

筆者がハマったポイントとして、dotenv.config() を main 関数の外に書くと、server.connect() の前に実行されてしまい、stdio の通信に混入してテスト時にエラーになりました。
dotenv の呼び出し位置には注意が必要です。

async function main() {
    const transport = new StdioServerTransport();
    await server.connect(transport);

    // 現在のファイルディレクトリを取得
    const __dirname = dirname(fileURLToPath(import.meta.url));
    // src / dist どちらから実行してもルートを指すよう調整
    const rootDir = resolve(__dirname, '..', '..');
    const envPath = resolve(rootDir, '.env');
    dotenv.config({ path: envPath });
}

Tools として SELECT クエリ専用 API を公開

SELECT クエリを安全に実行できるツールを公開します。
スキーマやテーブル一覧の取得も可能です。
(セキュリティ上の制約など、改善案があればコメントでぜひ教えてください 🙏)

server.setRequestHandler(ListToolsRequestSchema, async () => {
    return {
        tools: [
            {
                name: "query_database",
                description: "SQL Server データベースに対して SELECT クエリを実行します(読み取り専用)",
                inputSchema: {
                    type: "object",
                    properties: {
                        query: {
                            type: "string",
                            description: "実行する T-SQL クエリ(SELECT のみ)",
                        },
                    },
                    required: ["query"],
                },
            },
            {
                name: "get_table_schema",
                description: "テーブルのスキーマ情報を取得します",
                inputSchema: {
                    type: "object",
                    properties: {
                        table_name: {
                            type: "string",
                            description: "テーブル名(スキーマ付き可: dbo.Customers)",
                        },
                    },
                    required: ["table_name"],
                },
            },
            {
                name: "list_tables",
                description: "データベース内のテーブル一覧を取得します",
                inputSchema: {
                    type: "object",
                    properties: {
                        schema: {
                            type: "string",
                            description: "スキーマ名(デフォルト: dbo)",
                            default: "dbo",
                        },
                    },
                },
            }
        ],
    };
});

SELECT 専用の安全実行

CallToolRequestSchema の詳細な実装(DB 接続処理をふくむ部分)は本記事では割愛しますが、ここではツール query_database について補足します。
このツールでは自由に SQL クエリを入力できる仕様にしています。
ただし、誤って更新系の SQL を実行してしまうリスクを防ぐために、 DB の権限を読み取り専用に設定することに加えて、以下のような簡易的なクエリ制限を追加しています。

function isSafeQuery(query: string): boolean {
    const normalized = query.trim().toUpperCase();

    // SELECT または WITH で始まるかチェック
    const isSelectQuery = /^(SELECT|WITH)\b/.test(normalized);
    if (!isSelectQuery) return false;

    // 危険なキーワード一覧
    const dangerous = [
        "DROP", "DELETE", "INSERT", "UPDATE", "ALTER",
        "CREATE", "TRUNCATE", "EXEC", "EXECUTE", "SP_", "XP_"
    ];

    // 危険キーワードが含まれていれば false
    const containsDangerous = dangerous.some(keyword =>
        new RegExp(`\\b${keyword}\\b`, "i").test(normalized)
    );

    return !containsDangerous;
}

エラーや実行 SQL をログファイルに出力

stderr とログファイルの両方に出力することで、通信を壊さずに記録を残せます(MCP クライアントは stderr をログ扱いで無視するため安全)。

function log(level: LogLevel, message: string, data?: any) {
    const timestamp = new Date().toISOString();
    const logMessage = data
        ? `[${timestamp}] [${level}] ${message} ${JSON.stringify(data)}`
        : `[${timestamp}] [${level}] ${message}`;

    // stderr 出力(MCP 要件)
    console.error(logMessage);

    // ファイルにも追記
    try {
        appendFileSync(LOG_FILE, logMessage + '\n', 'utf8');
    } catch (error) {
        console.error(`Failed to write log to file: ${error}`);
    }
}

MCP Inspector で動作確認

以下のコマンドから MCP Inspector を起動します。

npx @modelcontextprotocol/inspector tsx src/index.ts

実行するとブラウザが起動し、MCP サーバーのテスト UI が表示されます。

MCP Inspector 起動

Connect → List Tools をクリックすると、サーバーが公開しているツール一覧を確認できます。

MCP Inspector ツール一覧

get_table_schema を選んで実行すれば、指定テーブルのスキーマ情報を取得可能です。

MCP Inspector ツール実行

Claude Desktop

MCP 設定

ここまで動作確認ができたら、Claude Desktop に MCP 設定を追加します。

{
  "mcpServers": {
    "qiita-mcp": {
      "command": "node",
      "args": ["/absolute/path/to/dist/index.js"]
    }
  }
}

再起動後、「設定 → コネクタ」に MCP サーバーを確認できます。正しく設定されていれば、ツール一覧を開いて接続できます。

いざ実演 — Claude に話しかける

以下のスクショのように Claude に話しかけると、MCP サーバーが SQL を生成し、DB から結果を取得して回答してくれます。

Claude Desktop プロンプト例 Claude Desktop 回答例

ここまでやってみて(所感)

LLM(Claude)を通じて自然言語で社内 DB の情報を取得できることを確認しました。
SQL 変換処理を一切書かず実現できた点に、非常に可能性を感じます。

MCP は “AI の USB-C” とも言われており、Claude Desktop 以外のクライアントからも同様に接続可能になると考えています。

今後は、特定の売上ビュー専用ツールなどを実装し、より実務的な応用を進めたいと考えています。
また、今回はローカル実行でしたが、次は Streamable HTTP サーバー としてリモートデプロイにも挑戦したいです。

改善点や提案などがあれば、ぜひコメントで教えてください!

58
46
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
58
46

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?