このブログ記事を初学者にもわかりやすく解説します。かなり長くなりますが、丁寧に説明していきます。
一言で言うと: AIエージェントが外部ツール(Google Drive、Salesforceなど)と連携する際、従来の方法だと大量のトークン(≒ 処理コスト)を消費してしまう。コード実行という手法を使えば、これを劇的に削減できる、という内容です。
この記事の核心は、 「AI が得意なことは AI に、プログラムが得意なことはプログラムに」 という考え方です。
MCP(Model Context Protocol) は、AI エージェントと外部システムを接続するための「共通規格」です。
たとえ話で説明すると:
従来のやり方は、家電製品ごとに専用のリモコンが必要な状態に似ています。テレビ用、エアコン用、照明用…とバラバラでした。MCP は「学習リモコン」のようなもので、一度設定すれば様々な機器を操作できます。
【従来】
AIエージェント ─専用コネクタ→ Google Drive
AIエージェント ─専用コネクタ→ Salesforce
AIエージェント ─専用コネクタ→ Slack
(それぞれ個別に開発が必要)
【MCP導入後】
AIエージェント ─MCP→ Google Drive
├MCP→ Salesforce
└MCP→ Slack
(共通プロトコルで全部つながる)
MCP は便利ですが、接続するツールが増えると2つの大きな問題が発生します。
コンテキストウィンドウとは: AIモデルが一度に処理できるテキストの量(トークン数)の上限です。
AIエージェントがツールを使うには、まず「どんなツールがあるか」をモデルに教える必要があります。これがツール定義です。
例:Google Driveのツール定義
────────────────────────────
gdrive.getDocument
説明: Google Driveからドキュメントを取得する
パラメータ:
- documentId(必須、文字列): 取得するドキュメントのID
- fields(任意、文字列): 返す特定のフィールド
戻り値: タイトル、本文、メタデータなどを含むドキュメントオブジェクト
1つのツール定義だけでも結構な量のテキストです。これが数百〜数千ツールになると、ユーザーの質問を読む前に何十万トークンも消費してしまいます。
具体的なイメージ:
【コンテキストウィンドウの使われ方】
┌─────────────────────────────────────┐
│ ツール定義 1,000個分 │ ← 150,000トークン(コストと時間がかかる)
│ (Google Drive, Salesforce, │
│ Slack, GitHub, Notion...) │
├─────────────────────────────────────┤
│ ユーザーの質問 │ ← 100トークン
├─────────────────────────────────────┤
│ AIの回答 │
└─────────────────────────────────────┘
もう一つの問題は、ツールの実行結果(中間結果)がすべてモデルを通過することです。
例:会議の議事録をGoogle DriveからSalesforceに転記する
ユーザーの依頼:
「Google Driveの会議議事録をダウンロードして、Salesforceのリードに添付して」
【従来のやり方】
──────────────────────────────────────────────────────────
ステップ1: Google Driveからドキュメント取得
TOOL CALL: gdrive.getDocument(documentId: "abc123")
→ 結果: "Q4の目標について議論...[議事録全文]"
→ この全文がAIのコンテキストに読み込まれる(例:50,000トークン)
ステップ2: Salesforceに書き込み
TOOL CALL: salesforce.updateRecord(...)
→ AIが議事録全文を「もう一度書き出す」必要がある(また50,000トークン)
合計:100,000トークン消費!
2 時間の会議の議事録だと、これだけで10万トークン近く使ってしまう可能性があります。
図解:従来のアーキテクチャ
┌─────────────────────────────────────────────────────────┐
│ LLM(AIモデル) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ コンテキストウィンドウ │ │
│ │ ・全ツール定義 │ │
│ │ ・ユーザーの質問 │ │
│ │ ・ツール実行結果(全文)← これが問題! │ │
│ └─────────────────────────────────────────────────┘ │
└────────────────────┬────────────────────────────────────┘
│ ↑↓ すべてのデータが通過
┌────────┴────────┐
│ MCPクライアント │
└────────┬────────┘
┌──────────┼──────────┐
↓ ↓ ↓
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Google │ │Salesforce│ │ Slack │
│ Drive │ │ MCP │ │ MCP │
│ MCP │ │ Server │ │ Server │
└──────────┘ └──────────┘ └──────────┘
従来は「AIが直接ツールを呼び出す」方式でしたが、新しいアプローチでは「AIがコードを書いて、そのコードがツールを呼び出す」方式を採用します。
従来の方式(直接ツール呼び出し):
AI → [ツール1を呼ぶ] → 結果をAIに返す → [ツール2を呼ぶ] → 結果をAIに返す
新しい方式(コード実行):
AI → [コードを書く] → コード実行環境 → [ツール1→ツール2] → 最終結果だけAIに返す
MCPサーバーのツールを、ファイルシステム上のコードファイルとして表現します。
servers/(ディレクトリ)
├── google-drive/
│ ├── getDocument.ts ← ドキュメント取得の関数
│ ├── getSheet.ts ← スプレッドシート取得の関数
│ └── index.ts
├── salesforce/
│ ├── updateRecord.ts ← レコード更新の関数
│ ├── query.ts ← クエリ実行の関数
│ └── index.ts
└── slack/
├── postMessage.ts
└── index.ts
各ファイルには、ツールを呼び出すためのTypeScript関数が定義されています。
// ./servers/google-drive/getDocument.ts
import { callMCPTool } from "../../../client.js";
interface GetDocumentInput {
documentId: string;
}
interface GetDocumentResponse {
content: string;
}
/* Google Driveからドキュメントを読み取る */
export async function getDocument(
input: GetDocumentInput,
): Promise<GetDocumentResponse> {
return callMCPTool<GetDocumentResponse>("google_drive__get_document", input);
}
先ほどの「議事録を Google Drive から Salesforce に転記」の例が、こうなります。
// AIが生成するコード
import * as gdrive from "./servers/google-drive";
import * as salesforce from "./servers/salesforce";
// Google Driveから議事録を取得
const transcript = (await gdrive.getDocument({ documentId: "abc123" })).content;
// Salesforceに書き込み(変数を渡すだけ!)
await salesforce.updateRecord({
objectType: "SalesMeeting",
recordId: "00Q5f000001abcXYZ",
data: { Notes: transcript },
});
重要なポイント:
transcript)はコード実行環境の中だけで流れる【従来の方式】
全ツール定義をロード:150,000トークン
【コード実行方式】
必要なツールのファイルだけ読む:2,000トークン
削減率:98.7%
AI はファイルシステムの探索が得意です。必要なツールだけを「必要なときに」読み込めます。
【従来】起動時に全ツール定義をロード
─────────────────────────────
1,000個のツール定義 → 一気にコンテキストへ
【コード実行方式】必要なときに必要なものだけ
─────────────────────────────
1. ./servers/ ディレクトリを一覧表示
2. 「今回はGoogle DriveとSalesforceが必要だな」
3. その2つのフォルダだけ読む
4. さらにその中で必要な関数のファイルだけ読む
また、search_toolsというツール検索機能を追加することで、さらに効率的にツールを見つけられます。
大量のデータを扱う場合、コード実行環境内でフィルタリングしてから結果を返せます。
例:10,000行のスプレッドシートから「保留中」の注文だけ取得
// 従来方式:10,000行すべてがAIのコンテキストに入る
TOOL CALL: gdrive.getSheet(sheetId: 'abc123')
→ 10,000行がコンテキストに... 💀
// コード実行方式:フィルタリングしてから返す
const allRows = await gdrive.getSheet({ sheetId: 'abc123' });
const pendingOrders = allRows.filter(row =>
row["Status"] === 'pending' // 「保留中」のものだけ
);
console.log(`Found ${pendingOrders.length} pending orders`);
console.log(pendingOrders.slice(0, 5)); // 最初の5件だけ表示
// AIは5行しか見なくていい!
ループ、条件分岐、エラーハンドリングを普通のコードで書けます。
例:Slackでデプロイ完了通知を待つ
// コード実行方式:ループをコードで書ける
let found = false;
while (!found) {
const messages = await slack.getChannelHistory({ channel: "C123456" });
found = messages.some((m) => m.text.includes("deployment complete"));
if (!found) await new Promise((r) => setTimeout(r, 5000)); // 5秒待機
}
console.log("Deployment notification received");
従来方式だと、このループの各反復で AI モデルを呼び出す必要があり、非効率でした。
中間データはコード実行環境内に留まるため、AIに見せたくないデータを保護できます。
例:顧客の個人情報をGoogle DriveからSalesforceに転記
const sheet = await gdrive.getSheet({ sheetId: "abc123" });
for (const row of sheet.rows) {
await salesforce.updateRecord({
objectType: "Lead",
recordId: row.salesforceId,
data: {
Email: row.email, // 実際のメールアドレス
Phone: row.phone, // 実際の電話番号
Name: row.name, // 実際の名前
},
});
}
console.log(`Updated ${sheet.rows.length} leads`);
MCPクライアントが自動的に個人情報をトークン化(匿名化)できます。
// AIが見るデータ(トークン化済み)
[
{ salesforceId: '00Q...', email: '[EMAIL_1]', phone: '[PHONE_1]', name: '[NAME_1]' },
{ salesforceId: '00Q...', email: '[EMAIL_2]', phone: '[PHONE_2]', name: '[NAME_2]' },
...
]
実際のデータはGoogle Drive → Salesforceに流れますが、AIのコンテキストには入りません。
コード実行環境ではファイルシステムにアクセスできるため、中間結果を保存して後で使うことができます。
// データを取得してCSVに保存
const leads = await salesforce.query({
query: "SELECT Id, Email FROM Lead LIMIT 1000",
});
const csvData = leads.map((l) => `${l.Id},${l.Email}`).join("\n");
await fs.writeFile("./workspace/leads.csv", csvData);
// 後で再開するときに読み込む
const saved = await fs.readFile("./workspace/leads.csv", "utf-8");
さらに、よく使う処理を「スキル」として保存し、再利用できます。
// スキルとして保存:./skills/save-sheet-as-csv.ts
import * as gdrive from "./servers/google-drive";
export async function saveSheetAsCsv(sheetId: string) {
const data = await gdrive.getSheet({ sheetId });
const csv = data.map((row) => row.join(",")).join("\n");
await fs.writeFile(`./workspace/sheet-${sheetId}.csv`, csv);
return `./workspace/sheet-${sheetId}.csv`;
}
// 後で簡単に使える
import { saveSheetAsCsv } from "./skills/save-sheet-as-csv";
const csvPath = await saveSheetAsCsv("abc123");