API × MCP 連携設計

API連携の断絶を解消するMCP実装ガイド:社内システムとLLMをつなぐ設計手法

この記事は急速に進化する技術について解説しています。最新情報は公式ドキュメントをご確認ください。

約9分で読めます
文字サイズ:
API連携の断絶を解消するMCP実装ガイド:社内システムとLLMをつなぐ設計手法
目次

この記事の要点

  • 既存APIとAIエージェントの安全かつ効率的な連携手法
  • 技術的負債を解消し、開発・保守コストを削減するMCP設計
  • AI連携におけるセキュリティ、ガバナンス、コンプライアンスの確保

本チュートリアルのゴール:MCPが解決する「API連携の断絶」

LLM(大規模言語モデル)の業務活用が進む中、多くの組織が直面するのが「社内データや既存システムとの連携」という壁です。本記事では、Anthropicが提唱する「Model Context Protocol(MCP)」を用いて、既存の社内REST APIをLLMの「道具(ツール)」として安全かつ効率的に統合する設計手法を解説します。

なぜこれまでのAPI連携は非効率だったのか

LLMに外部APIを叩かせる従来の独自実装では、プロンプト内にAPIの仕様やエンドポイント、リクエスト形式を長々と記述する必要がありました。これはプロンプトの肥大化を招き、トークン消費量を増加させるだけでなく、LLMがハルシネーション(もっともらしい嘘)を起こして誤ったパラメータでAPIを呼び出すリスクを孕んでいました。また、システムごとに異なる認証方式やデータフォーマットの不一致といった「API連携の断絶」は、開発現場において珍しい課題ではありません。

MCP(Model Context Protocol)がもたらす標準化のメリット

MCPは、LLMと外部データソース(API、データベース、ローカルファイルなど)を接続するための「標準化されたインターフェース」です。クライアント(LLMアプリ)とサーバー(データソース側)を分離し、共通のプロトコルで通信を行うことで、LLMはAPIの複雑な内部仕様を知る必要がなくなります。公式ドキュメントに記載されている通り、MCPは「Resources(データ読み取り)」「Tools(アクション実行)」「Prompts(定型指示)」という3つの主要機能を提供します。本チュートリアルを通じて、社内APIをMCPサーバーとしてラップし、LLMに安全なアクセス権を付与する一連の開発フローをマスターしていきましょう。

ステップ1:開発環境の構築とMCP SDKのセットアップ

MCPサーバーの実装には、型安全性が高くJSON Schemaとの相性が良いTypeScript(Node.js)の利用が一般的に推奨されます。ここでは、TypeScriptを用いたプロジェクトの立ち上げ手順を解説します。

必要なツール(Node.js/TypeScript)の準備

開発を始める前に、最新のNode.js環境がインストールされていることを確認してください。バージョン管理ツール(nvmなど)を使用して、安定版(LTS)を利用することを推奨します。

# Node.jsのバージョン確認
node -v
npm -v

MCP SDKのインストールとプロジェクト初期化

適当な作業ディレクトリを作成し、npmプロジェクトを初期化します。その後、Anthropicが提供する公式のMCP SDKと、TypeScriptの実行に必要なパッケージをインストールします。

mkdir mcp-api-server
cd mcp-api-server
npm init -y

# MCP SDKのインストール
npm install @modelcontextprotocol/sdk

# TypeScriptと開発用ツールのインストール
npm install -D typescript @types/node ts-node

次に、tsconfig.json を生成し、TypeScriptの設定を行います。

npx tsc --init

tsconfig.json の内容を以下のように調整し、モダンなモジュール解決ができるようにします。

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "./dist"
  },
  "include": ["src/**/*"]
}

これで、MCPサーバーを開発するための基本的な土台が整いました。

ステップ2:リソース(Resources)の定義:静的データへのアクセス設計

ステップ1:開発環境の構築とMCP SDKのセットアップ - Section Image

MCPにおける「Resources」は、LLMに対して読み取り専用のデータを提供する機能です。社内Wikiのドキュメントや、設定ファイル、データベースの参照など、副作用を伴わない静的なデータアクセスに適しています。

URIスキームを用いたデータ特定

Resourcesは、file:// や独自に定義した custom-api:// などのURIスキームを用いてデータを特定します。以下のコードは、社内の従業員ディレクトリAPIからデータを取得し、LLMに提供するResourceの実装例です。

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { ListResourcesRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";

// MCPサーバーのインスタンス化
const server = new Server(
  {
    name: "employee-directory-server",
    version: "1.0.0",
  },
  {
    capabilities: {
      resources: {},
    },
  }
);

// リソース一覧の定義
server.setRequestHandler(ListResourcesRequestSchema, async () => {
  return {
    resources: [
      {
        uri: "api://employees/directory",
        name: "Employee Directory",
        mimeType: "application/json",
        description: "社内の全従業員リストと部署情報を含むディレクトリデータ",
      },
    ],
  };
});

LLMが読み取り可能なメタデータ記述のコツ

LLMがデータを正確に解釈するためには、mimeTypedescription の記述が極めて重要です。単に「従業員データ」と書くのではなく、「社内の全従業員リストと部署情報を含むディレクトリデータ」のように、データに何が含まれているかを具体的に記述します。

// リソースの読み取り処理
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  if (request.params.uri === "api://employees/directory") {
    // 実際の社内APIを呼び出す想定のモックデータ
    const employeeData = [
      { id: "E001", name: "山田 太郎", department: "情報システム部" },
      { id: "E002", name: "佐藤 花子", department: "営業部" }
    ];

    return {
      contents: [
        {
          uri: request.params.uri,
          mimeType: "application/json",
          text: JSON.stringify(employeeData, null, 2),
        },
      ],
    };
  }
  throw new Error("Resource not found");
});

このように設計することで、LLMは必要な時に自律的にこのURIを参照し、コンテキストとして情報を読み込むことが可能になります。

ステップ3:ツール(Tools)の実装:副作用を伴うAPI操作の設計

データの更新、メールの送信、チケットの起票など、システムに状態変化(副作用)をもたらす操作は「Tools」として実装します。LLMが外部APIを呼び出す際のインターフェースとなる最も重要なセクションです。

JSON Schemaによる引数の厳密な定義

LLMにツールを使わせる際、最もつまずきやすいのが「引数の型不一致」によるエラーです。これを防ぐためには、JSON Schemaを用いて引数の構造を厳密に定義し、さらに各プロパティに詳細な説明(description)を付与することが不可欠です。

import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";

// ツール一覧の定義
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: "create_support_ticket",
        description: "社内ヘルプデスクシステムに新しいサポートチケットを起票します。",
        inputSchema: {
          type: "object",
          properties: {
            title: {
              type: "string",
              description: "チケットのタイトル。簡潔に課題を要約してください。",
            },
            priority: {
              type: "string",
              enum: ["low", "medium", "high", "critical"],
              description: "インシデントの優先度。",
            },
            description: {
              type: "string",
              description: "問題の詳細な説明。発生日時やエラーメッセージを含めてください。",
            },
          },
          required: ["title", "priority", "description"],
        },
      },
    ],
  };
});

ここで重要なのは、enum を用いて許容される値を制限することと、description でLLMに「どのような形式で値を渡すべきか」を指示することです。この設計が甘いと、LLMは独自の判断で存在しない優先度(例:"urgent")を指定してしまう可能性があります。

既存APIエンドポイントとのマッピング手法

ツールが呼び出された際の処理(CallToolRequestHandler)を実装します。ここで、LLMから渡された引数を既存のREST APIのリクエスト形式に変換(マッピング)して送信します。

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.name === "create_support_ticket") {
    // 引数の型アサーション
    const args = request.params.arguments as {
      title: string;
      priority: string;
      description: string;
    };

    try {
      // 既存の社内APIへのPOSTリクエストをシミュレート
      // 実際には fetch や axios を使用してAPI通信を行います
      const apiResponse = {
        status: "success",
        ticketId: "TICKET-" + Math.floor(Math.random() * 10000),
        message: "チケットが正常に作成されました。",
      };

      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(apiResponse, null, 2),
          },
        ],
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `API通信エラー: ${error instanceof Error ? error.message : String(error)}`,
          },
        ],
        isError: true, // LLMにエラーが発生したことを明示的に伝える
      };
    }
  }
  throw new Error("Tool not found");
});

エラーハンドリングを適切に行い、isError: true を返すことで、LLMは「実行に失敗した」ことを認識し、引数を修正して再試行するなどのリカバリー行動をとることが可能になります。

ステップ4:認証とセキュリティ:安全な中継層の設計

ステップ3:ツール(Tools)の実装:副作用を伴うAPI操作の設計 - Section Image

社内APIをLLMから呼び出す場合、セキュリティの確保は最優先課題です。MCPサーバーは、LLMと社内システムの間の中継層(プロキシ)として機能するため、ここで適切なアクセス制御を行う必要があります。

APIキー・OAuthトークンの秘匿管理

LLMのプロンプトやクライアント側に社内APIの認証情報を持たせることは避けるべきです。認証情報はMCPサーバー側で環境変数として保持し、サーバーがAPIを呼び出す際にヘッダーに付与する設計とします。

// 環境変数からAPIキーを取得
const INTERNAL_API_KEY = process.env.INTERNAL_API_KEY;

if (!INTERNAL_API_KEY) {
  console.error("環境変数 INTERNAL_API_KEY が設定されていません。");
  process.exit(1);
}

// API呼び出し時のヘッダー設定例
/*
const response = await fetch("https://api.example.com/v1/tickets", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": `Bearer ${INTERNAL_API_KEY}`
  },
  body: JSON.stringify(payload)
});
*/

ローカル環境と本番環境での接続設定

MCPサーバーをClaude Desktopなどのクライアントアプリケーションと接続するには、設定ファイル(claude_desktop_config.json)を編集します。この設定ファイル内で、MCPサーバーの起動コマンドと、必要な環境変数を渡します。

{
  "mcpServers": {
    "internal-api-server": {
      "command": "node",
      "args": ["/path/to/mcp-api-server/dist/index.js"],
      "env": {
        "INTERNAL_API_KEY": "your_secure_api_key_here"
      }
    }
  }
}

この設計により、LLM自体はAPIキーの存在を知ることなく、安全に社内システムと対話することができます。本番環境へのデプロイにおいては、AWS Secrets ManagerやHashiCorp Vaultなどのシークレット管理サービスと連携することで、さらなるセキュリティ強化が図れます。

ステップ5:デバッグと検証:LLMとの疎通確認

実装したMCPサーバーを本番投入する前に、意図した通りに動作するかを検証するプロセスが不可欠です。

MCP Inspectorを用いた単体テスト

Anthropicは、MCPサーバーの開発とデバッグを支援する公式ツールとして「MCP Inspector」を提供しています。これを使用することで、LLMを介さずにMCPサーバーのResourcesやToolsをブラウザ上から直接テストすることができます。

# サーバーのビルド
npx tsc

# MCP Inspectorの起動
npx @modelcontextprotocol/inspector node dist/index.js

Inspector上で、定義したツール(create_support_ticket など)を選択し、手動でJSON形式の引数を入力して実行結果を確認します。ここで型エラーや通信エラーが発生しないことを確認することが、最初の関門となります。

実際のLLMクライアントでの動作確認

Inspectorでの単体テストに合格したら、Claude Desktopなどの対応クライアントから実際のLLMモデルと対話して疎通確認を行います。

プロンプト例:
「社内システムでエラーが発生しました。優先度を高(high)にして、サポートチケットを起票してください。タイトルは『DB接続エラー』、詳細は『タイムアウトが頻発しています』としてください。」

LLMがMCPサーバーのツールを呼び出し、成功のレスポンスを受け取って「チケット(TICKET-XXXX)を起票しました」と自然言語で回答できれば、API連携は成功です。もしLLMが意図しない引数を生成した場合は、ステップ3で解説したJSON Schemaの description を見直し、より明確な指示を記述するチューニングを行います。

さらなる応用:プロンプトテンプレートの活用と将来の拡張性

基本となるResourcesとToolsの実装が完了したら、MCPのもう一つの強力な機能である「Prompts」を活用することで、さらなるUXの向上が期待できます。

Prompts機能による定型タスクの自動化

Promptsは、再利用可能なプロンプトのテンプレートをMCPサーバー側で定義する機能です。例えば「コードレビュー依頼」や「日報の自動生成」など、頻繁に行うタスクのプロンプト構造をサーバー側で管理することで、ユーザーは複雑な指示を毎回入力する手間から解放されます。

マルチサーバー構成への発展と継続的な学習

実際の企業環境では、人事システム、CRM、開発ツールなど、複数の独立したMCPサーバーを立ち上げ、LLMがそれらを横断的に利用するマルチサーバー構成への発展が考えられます。これにより、LLMは「人事データを確認し、該当者のCRMアカウントを更新する」といった、部門を跨いだ自律的なエージェントとして機能するようになります。

AIと外部システムの統合技術は、日々急速な進化を遂げています。MCPのような新しいプロトコルや、LLMのエージェント化に関する最新動向をキャッチアップするには、技術ブログや公式ドキュメントを定期的に確認するだけでなく、X(旧Twitter)やLinkedInなどのSNSで専門家の発信を継続的にフォローし、情報収集の仕組みを整えることをおすすめします。自社のインフラにAIを安全かつ効果的に組み込むための第一歩として、ぜひ本チュートリアルの実装に挑戦してみてください。

参考リンク

ステップ4:認証とセキュリティ:安全な中継層の設計 - Section Image 3

API連携の断絶を解消するMCP実装ガイド:社内システムとLLMをつなぐ設計手法 - Conclusion Image

参考文献

  1. https://ai-insight.jp/news/claude-mythos-preview-20260422/
  2. https://www.watch.impress.co.jp/docs/news/2102748.html
  3. https://biz.moneyforward.com/ai/basic/4831/
  4. https://genai-ai.co.jp/ai-kanri/blog/cc-claude-safety-guide/
  5. https://note.com/sakaihiroshi_tax/n/n5c08132223fd
  6. https://japan.zdnet.com/article/35247263/
  7. https://www.youtube.com/watch?v=6xJtMNq9JcE

コメント

コメントは1週間で消えます
コメントを読み込み中...