0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

MCP サーバーに本番 DB を触らせない — AI エージェントは「文書化された API 境界」を通す

0
Last updated at Posted at 2026-07-12

はじめに

MCP(Model Context Protocol)でエージェントに社内システムを触らせる話が増えてきました。そこでよく見かけるのが、こういう構成です。

AI エージェント
  → MCP ツール(SQL 実行)
  → 本番データベース

「DB に SELECT できる MCP サーバーを繋いだら、AI がなんでも答えてくれるようになった」— デモとしては最高に映えます。僕も誘惑は分かります。

でも業務システムでこれをやってはいけない、というのがこの記事の主張です。代わりにこう組みます。

AI エージェント
  → MCP ツール
  → 文書化された HTTP API(OpenAPI)
  → ハンドラ → ユースケース → リポジトリ

抽象論で終わらせないために、後半は自作の OSS(PHP 製 stdio MCP ブリッジ nene-mcp)で実際にどう実装したかを見せます。

DB 直結の何が問題か — DB はルールを知らない

「読み取り専用の SELECT なら安全でしょ?」から検討します。答えは No です。

アプリケーション層は、DB が知らないルールを大量に持っています。

アプリ層が持つ知識 DB 直結で何が起きるか
テナント分離 WHERE organization_id = ?エージェントの善意に任せることになる。1つの JOIN ミスで他社のデータが LLM のコンテキストに載る
認可 「経理ロールだけが見られる」を SQL は知らない
ドメイン不変条件 「発行済み請求書の金額は変更不可」はアプリのコード
監査ログ アプリを迂回した読み書きは監査に残らない
バリデーション 書き込みなら言わずもがな
エラー契約・リクエスト ID 障害調査の紐が切れる

重要なのは、この表の上2行は SELECT だけでも破られることです。「read-only だから安全」は、シングルテナントで全データ公開可能な場合にしか成立しません。

もうひとつ。DB スキーマは非公開の実装詳細です。エージェントに SQL を書かせると、スキーマが事実上の公開 API になり、カラム名ひとつ変えるだけで誰かのエージェントが黙って壊れるようになります。

MCP ツールは「アプリの能力」を公開するもの

MCP の価値は、エージェントに構造化された能力の呼び出し方を与えることです。その能力はアプリケーションが既に持っています — バリデーションも認可も監査も込みで。

だから MCP ツールがやるべきことは、通常のクライアントが呼ぶのと同じ文書化された API を呼ぶこと。それだけです。

  • エージェントがレコードの状態を知りたい → 普通のクライアントと同じ read API を呼ぶ
  • エージェントが何かを作成・更新したい → 既に検証・認証・ログ・エラー契約を持つ write API を呼ぶ

MCP はアプリの能力を公開する層であって、第二の隠れたアプリケーションを発明する場所ではない。地味な結論ですが、地味なのが正解です。エージェントにも読めて、開発者にもレビューできて、テストも書ける形は、結局いつものアーキテクチャです。

実装: OpenAPI をツール契約として使う

nene-mcp は、コミットされたカタログ tools.json を読んで HTTP API を MCP ツールとして公開する小さな stdio ブリッジです(newline-delimited JSON-RPC / initializetools/listtools/call)。

カタログの実物はこういう形をしています。

{
  "name": "getHealth",
  "title": "Health",
  "description": "Read the health endpoint through the documented public API.",
  "safety": "read",
  "source": {
    "type": "openapi",
    "operationId": "getHealth",
    "method": "GET",
    "path": "/health"
  },
  "inputSchema": {
    "type": "object",
    "additionalProperties": false,
    "properties": {}
  },
  "responseSchemaRef": "#/components/schemas/HealthResponse"
}

見てのとおり、ツール1つ = OpenAPI の operation 1つです。ツールが知っているのは:

  • ツール名と入力の JSON Schema
  • HTTP メソッドとパス(OpenAPI の operationId に紐づく)
  • API のベース URL と、必要なら資格情報

ツールが知らないもの: SQL、DB の接続文字列、.env の中身、データがどこにどう保存されているか。 API が契約のすべてで、契約は OpenAPI として既に文書化・テスト済みのものを使い回します。エージェント用に新しい契約を発明しません。

実装: read から始めて、write は fail-closed

カタログの各ツールには safety を必ず書きます。値は readwrite の二値で、それ以外はカタログの検証段階で弾かれます。

if ($safety !== 'read' && $safety !== 'write') {
    throw new InvalidArgumentException(sprintf(
        'MCP catalog field "safety" must be "read" or "write", got "%s".', $safety,
    ));
}

この safety は飾りではなく、2つの実挙動に効きます。

1. MCP クライアントへのヒント。 safety: read のツールは tools/list 応答で readOnlyHint アノテーションが付き、クライアント側の確認 UI が適切に振る舞えます。

2. write の fail-closed。 認証が設定されていない状態では、read 以外のツールは呼び出し自体を拒否します。

if ($tool['safety'] !== 'read' && !$this->httpClient->hasAuthentication()) {
    throw new McpRuntimeException(sprintf(
        'Write tool "%s" requires bearer authentication. '
        . 'Set NENE_MCP_BEARER_TOKEN in the MCP server environment.',
        $name,
    ));
}

「認証を設定し忘れたら write が素通しになる」ではなく、「設定し忘れたら write は動かない」に倒してあります。デフォルトで安全側、が MCP のような開発者ツーリングでは特に重要です — この手の設定は雑にコピペされて広まるので。

導入の順番も read からを勧めます。ヘルスチェック、一覧取得、1件取得、履歴の照会。状態を変えないツールはエージェントとの相互作用を学ぶ教材として安全で、それだけでも「ダッシュボードを要約して」のようなユースケースは十分成立します。write ツールは通常の API 設計と同じ水準の設計 — 認証・認可・監査・失敗系のテスト — を済ませてから足すもので、週末のショートカットではありません。

エラーを「Something went wrong」に潰さない

見落とされがちですが、エラーの中継は MCP ブリッジの重要な仕事です。

API が RFC 9457(Problem Details)で構造化エラーを返すなら、ツールはそれをそのままエージェントに見せるべきです。バリデーション違反なら何が悪いかの構造化された 400 を、認証切れなら構造化された 401 を。

エージェントは失敗から次の行動を決めます。「何かがうまくいきませんでした」に潰されたエラーからは、リトライすべきか、入力を直すべきか、人間を呼ぶべきかが判断できません。予測可能な失敗の形も AI-readability の一部です。

秘密情報はツールの外に

MCP の設定は開発者ツーリングの近く(エディタ設定・リポジトリ)に置かれがちなので、事故りやすいポイントを列挙しておきます。

  • 環境変数の名前は文書化する。はコミットしない
  • ツールのメタデータ(tools/list 応答)に資格情報を含めない
  • MCP ツールに生の .env を読ませない
  • トークンをログに出さない

ツールは「このスコープが必要」と言ってよい。資格情報そのものを晒してはいけない。

判定に迷ったときの2つの質問

新しい MCP ツールを設計するとき、僕はこの順で自問します。

Q1. 普通の HTTP クライアントが、文書化された API を通して同じことを安全にできるか?

Yes なら、そのツールはたぶん良いラッパーです。No なら次へ。

Q2. なぜその能力は、エージェント専用に存在するのか?

たまに正当な答えがあります。でも大抵はありません。エージェント専用の裏口が欲しくなったら、それは本物の業務機能が API・テスト・ドキュメント・レビューを欠いたまま生まれようとしているサインです。先にアプリの API にしてから、ツールで包んでください。

まとめ

  • DB は テナント分離・認可・不変条件・監査を知らない。SELECT だけでも境界は破れる
  • MCP ツールは文書化された HTTP API(OpenAPI の operation)と 1:1 に対応させる。契約を二重に発明しない
  • safety は二値で必須・write は認証未設定なら動かない(fail-closed)
  • エラーは Problem Details のまま中継する。構造化された失敗はエージェントの意思決定材料
  • 迷ったら「普通のクライアントが文書化 API で安全にできるか?」

nene-mcp は MIT ライセンスの OSS で、composer require hideyukimori/nene-mcp で入ります(Packagist / ドキュメント)。土台のフレームワーク側の設計は NENE2 にあります。想定スコープは開発者と小規模チームのサイドカー(Cursor / Claude Desktop / 社内ステージング)で、顧客向け API ゲートウェイではありません — そこも含めて「MCP はアプリを呼ぶ。アプリを迂回しない」が一貫した設計方針です。


── 筆者: 森 秀之(彩音インターナショナル)— 自己ホストの業務ツール群を実運用中。
中小企業向けの業務システムを料金公開・固定価格で開発しています。
🔗 ayane.co.jp

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?