友達からLINEが来た。
「明日の14時、乳製品アレルギーの友達も一緒にカフェ行きたいんだけど、メニュー大丈夫かな?予約もしておいて」
普通なら、食べログ開いて → アレルギー情報探して → 電話して → 空き確認して → 予約する。15分コース。
でも今回、AIにこう頼んだだけ:
「乳製品アレルギーの友達と明日14時、2名で予約して。名前は松原で」
数十秒後 ──
✅ 空席確認: 34席空き(店内34席中)
✅ メニュー検索: 乳製品フリー5品ヒット(エスプレッソ ¥400〜)
✅ 予約完了: AURA-20260305-003(松原様 2名 14:00 店内席)
全部、AIが勝手にやった。 検索→絞り込み→空き確認→予約。この4ステップをAIが判断して実行した。使ったのは MCP (Model Context Protocol) という仕組みで、TypeScript 2ファイルで実装できる。
この記事は 前回のLLM-SEO記事 の続編(完結編)です。前回未読でも完結しますが、読むと「騙す→読ませる→使わせる」の進化が3倍楽しくなります。
目次
「見せる → 読ませる → 使わせる」三部作の完結
前回の記事では、AIに 「読みやすいデータ」 を提供する3つの手法を検証しました。同じ情報なのにHTMLの正答率75%、Markdownなら100%。
でもあれ、よく考えると 「メニューの出し方を変えた」 だけですよね。
前回: 「日本語メニューしかない店」→「英語メニューも用意した」
↑ 読みやすくなっただけ。できることは変わらない。
今回: 「メニューを読む」→「電話で予約できる」
↑ 読むだけじゃない。AIが店を "使える" ようになった。
| Part | やったこと | レストランの例え |
|---|---|---|
| 1 | User-Agentで別ページを表示 | 外国人に勝手に英語メニューを出す |
| 2 | Markdown / JSON で情報提供 | 多言語メニューを用意する |
| 3 | MCP Server でツール提供 | 電話予約・アレルギー相談ができる |
MCP って何? ── 30秒で分かる説明
MCP (Model Context Protocol) は、AIエージェントが外部のツールやデータソースを「使う」ための標準プロトコル。Anthropic が策定し、2025年からエコシステムが急拡大中。
一言で言うと: MCPとは、AIに「うちのサービスの電話番号」を教えること。
引き続きレストランで例えます:
これまで(Part 1-2):
お客さん → メニューを読む → 「美味しそうだな」で終わり
MCP(Part 3):
お客さん → 電話する → 「明日2名で予約お願いします」
→ 「アレルギー対応できますか?」
→ 「キャンセルしたいんですけど」
メニューを読むだけじゃない。電話番号を教えて、予約システムにつなぐ。 それが MCP です。
いざ検証 ── AIにカフェを「使わせて」みた
百聞は一見にしかず。先に動いているところをお見せします。
検証環境: Claude Code(Claude Opus 4.6)+ cafe-aura MCP Server。結果は非決定的であり、毎回同じ結果になるとは限りません。ただし「AIがツールを選択・実行する」挙動は安定して観察されました。
Demo 1 / 4: 「WiFiのパスワード教えて」
まずはシンプルなところから。
私:
Cafe AURAのWiFiパスワードを教えて
Claude Code:
[MCP] get_wifi_info() を呼び出し中...
Cafe AURAのWiFi情報です:
- SSID: CAFE-AURA-FREE
- パスワード: aura2026
- 速度: 下り約200Mbps
- 制限: 1回の接続で最大3時間(再接続可)
前回の記事で HTMLから読んだら「3時間制限」を見落とした あの情報。MCPツール経由なら構造化データで返るから見落としようがない。
前回(HTML 132KB を渡した場合): ❌ 「3時間制限」を見落とし
今回(MCP get_wifi_info): ✅ 全情報を正確に返答
Demo 2 / 4: 「乳製品アレルギーなんだけど」
ここからが面白い。複数パラメータの同時指定です。
私:
乳製品アレルギーの友達と行くんだけど、予算600円以内で頼めるメニューある?
Claude Code:
[MCP] search_menu({
excludeAllergens: ["乳"],
maxPrice: 600
}) を呼び出し中...
乳製品フリーで600円以内のメニューが5品見つかりました:
1. シングルオリジン ¥580 - 月替わりの産地直送豆をハンドドリップで
2. コールドブリュー ¥530 - 16時間かけて水出しした滑らかな味わい ⭐NEW
3. ハーブティー ¥500 - カモミール / ペパーミント / ルイボスから選択
4. アールグレイ ¥480 - ベルガモットの香り高いクラシックティー
5. エスプレッソ ¥400 - 深煎りブレンドのダブルショット
全品アレルゲンフリーです。
「乳製品アレルギー」→ excludeAllergens: ["乳"]。 AIが自然言語をツールのパラメータに変換している。これ、地味にすごくないですか?(この秘密は後述する「description設計」セクションで明かします)
Demo 3 / 4: 「予約して」── 水曜の罠
本番です。予約フロー。
私:
じゃあ明日(水曜)の14時に2名で予約して。名前は松原で
Claude Code:
[MCP] get_seat_availability({
date: "2026-03-04",
time: "14:00"
}) を呼び出し中...
❌ 2026-03-04 は水曜日で定休日です。
木曜日の 2026-03-05 はいかがですか?
……マジか。AIが定休日を判断して、代替日を提案してきた。
私:
じゃあ木曜で
Claude Code:
[MCP] get_seat_availability({
date: "2026-03-05",
time: "14:00"
}) を呼び出し中...
空席あり: 店内34席中 34席空き
[MCP] make_reservation({
name: "松原",
date: "2026-03-05",
time: "14:00",
partySize: 2
}) を呼び出し中...
✅ 予約が完了しました!
予約番号: AURA-20260305-003
お名前: 松原様
日時: 2026年3月5日(木)14:00
人数: 2名
席: 店内席
※ 前日18:00までキャンセル無料です

↑ ユーザーは日本語で頼むだけ。AIが空席確認→予約作成を実行
ここ、よく見てください。 私がやったのは「木曜で」と3文字打っただけ。AIが:
- 「木曜」→
2026-03-05に変換 -
get_seat_availabilityで空き確認 - 空きがあったので
make_reservationで予約作成
この3ステップを判断して実行した。私はツール名を1つも指定していない。
Demo 4 / 4: 「やっぱキャンセルで」
私:
さっきの予約、やっぱりキャンセルしたい
Claude Code:
[MCP] cancel_reservation({
reservationId: "AURA-20260305-003"
}) を呼び出し中...
✅ 予約をキャンセルしました。
予約番号 AURA-20260305-003
(松原様 2名 2026-03-05 14:00)
AIは 会話の文脈から予約番号を覚えていて、自動でキャンセル処理を実行した。「予約番号は?」とか聞いてこない。
何が起きているのか ── 技術的に整理する
ポイントは AIが「どのツールを呼ぶか」を自分で決めている こと。
get_seat_availability → make_reservation の順序は私が指示していない。AIが:
- ツールの
descriptionを読む - ユーザーの意図を解釈する
- 最適なツールと呼び出し順序を決定する
- 結果を見て次のアクションを判断する
「読ませる」と「使わせる」の本質的な違い。Part 2 まではデータの受け渡し。Part 3 は判断と実行。
もう少し技術的に知りたい人向け
通信方式: Claude Code が MCP Server をサブプロセスとして起動し、stdin/stdout で JSON-RPC 2.0 メッセージをやり取りします。AIは起動時に全ツールの description を受け取り、どのツールをどの順序で呼ぶかを判断します。
実装 ── 架空カフェ「Cafe AURA」に MCP Server を作る
デモで動きを見たところで、中身の実装を見ていきましょう。
8つのツール
| # | ツール名 | 種別 | できること |
|---|---|---|---|
| 1 | get_cafe_info |
📘 INFO | 営業時間・住所・アクセス |
| 2 | get_wifi_info |
📘 INFO | SSID・パスワード・速度・制限 |
| 3 | get_menu |
🔍 SEARCH | メニュー一覧(カテゴリフィルタ) |
| 4 | search_menu |
🔍 SEARCH | アレルゲン除外・価格帯・キーワード検索 |
| 5 | get_seat_availability |
🔍 SEARCH | 日付・時間指定の空席確認 |
| 6 | make_reservation |
⚡ ACTION | 予約作成(定休日・定員チェック付き) |
| 7 | get_reservation |
⚡ ACTION | 予約番号で検索 |
| 8 | cancel_reservation |
⚡ ACTION | 予約キャンセル |

↑ 情報取得(青)→ 検索(緑)→ 予約管理(紫)の3レイヤー。AIはこれらを自由に組み合わせる
レストランで言えば: INFO=看板、SEARCH=メニュー表、ACTION=電話予約。段階的に「できること」が増える設計です。
プロジェクト構成
cafe-aura-mcp/
├── src/
│ ├── index.ts # MCP Server本体(8ツール登録)
│ └── data.ts # Cafe AURAデータ + 予約システム
├── package.json
├── tsconfig.json
└── dist/ # ビルド出力(node dist/index.js で起動)
ファイル2つ。 これだけです。
データ層 ── src/data.ts
前回の cafe-data.ts を data.ts にリネーム・拡張。メニュー14品にアレルゲン・カロリー情報を追加し、予約システム(インメモリ)を実装しました。席は全42席(店内34 + テラス8、テラスは予約不可)。
data.ts の主要部分(クリックで展開)
export const MENU_ITEMS: MenuItem[] = [
{
id: "specialty-latte",
name: "スペシャルティラテ",
price: 650,
category: "coffee",
description: "エチオピア・イルガチェフェ豆を使用した看板メニュー",
allergens: ["乳"], // ← アレルゲン情報
isPopular: true,
calories: 180,
},
// ... 全14品
];
予約システムはシンプルな Map ベース:
const reservations = new Map<string, Reservation>();
export function makeReservation(params: {
name: string;
date: string; // YYYY-MM-DD
time: string; // HH:MM
partySize: number;
seatType?: "indoor" | "any"; // テラスは予約不可
note?: string; // アレルギー情報、記念日など
}) {
// 水曜定休チェック
const d = new Date(params.date + "T00:00:00+09:00");
if (d.getDay() === 3) {
return { success: false, error: "水曜日は定休日です" };
}
// 空席チェック → 予約作成 → IDを返す
// ...
}
本番環境ではインメモリではなくDBを使ってください。 これはデモ用です。MCP Server のプロセスが終了すると予約データも消えます。
MCP Server 本体 ── src/index.ts
ここが この記事の核心 です。MCP SDK の registerTool を使ったツール登録がどれだけシンプルか見てください。
import + 初期化(クリックで展開)
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { CAFE_INFO, WIFI_INFO, MENU_ITEMS, /* ... */ } from "./data.js";
const server = new McpServer({
name: "cafe-aura",
version: "1.0.0",
});
ツール登録は server.registerTool() を呼ぶだけ。
server.registerTool(
"get_cafe_info",
{
description:
"Cafe AURAの基本情報(営業時間・住所・電話番号・アクセス・座席数・決済方法)を取得します",
},
async () => ({
content: [{ type: "text" as const, text: JSON.stringify(CAFE_INFO, null, 2) }],
})
);
第2引数のオブジェクトに注目。 description がツールの "看板" です。AIはこの説明文を読んで「どのツールを使うか」を判断します。
パラメータがあるツールは inputSchema を追加:
server.registerTool(
"search_menu",
{
description:
"条件を指定してメニューを検索します(アレルゲン除外・価格帯・キーワード)",
inputSchema: {
excludeAllergens: z.array(z.string()).optional()
.describe("除外するアレルゲン(例: ['乳', 'ナッツ'])"),
maxPrice: z.number().optional()
.describe("上限価格(円)"),
minPrice: z.number().optional()
.describe("下限価格(円)"),
keyword: z.string().optional()
.describe("メニュー名や説明文に含まれるキーワード"),
},
},
async ({ excludeAllergens, maxPrice, minPrice, keyword }) => {
let items = [...MENU_ITEMS];
if (excludeAllergens?.length) {
items = items.filter(
(item) => !item.allergens.some((a) => excludeAllergens.includes(a))
);
}
// maxPrice / minPrice / keyword フィルタも同様のパターン
// 0件の場合は「見つかりませんでした」メッセージを返す
return {
content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }],
};
}
);
起動処理(クリックで展開)
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
main().catch(console.error);
以上。 npm run build → node dist/index.js で起動。
description 設計 ── AIに「正しく使わせる」コツ
ここが MCP Server 開発で最も差がつくポイント です。
レストランの電話応対で例えると、description は 電話の自動音声ガイド にあたります。「1番を押すと営業時間、2番を押すとアクセス…」のように、AIに「この電話ではこれができます」と教える。
良い description の書き方:
| ❌ ダメな例 | ✅ 良い例 |
|---|---|
"メニューを検索" |
"条件を指定してメニューを検索します(アレルゲン除外・価格帯・キーワード)" |
"予約する" |
"Cafe AURAに予約を作成します。水曜定休。テラス席は予約不可(先着順)。前日18:00までキャンセル無料" |
"空席確認" |
"指定日(オプションで時間帯)の席の空き状況を確認します。テラス席は予約不可(先着順)" |
ポイントは3つ:
- 何ができるか を具体的に書く(「メニュー検索」ではなく「アレルゲン除外・価格帯・キーワードで検索」)
- 制約条件 を明記する(「水曜定休」「テラス席は予約不可」)
- 業務知識 を埋め込む(「前日18:00までキャンセル無料」)
同じく、Zod の .describe() も重要です:
// ❌ AIが何を入れていいか分からない
maxPrice: z.number().optional()
// ✅ AIが「600円以内」を 600 に変換できる
maxPrice: z.number().optional().describe("上限価格(円)")
description と .describe() をちゃんと書けば、AIは賢くツールを使ってくれます。 Demo 2で「乳製品アレルギー」→ ["乳"] に変換できたのも、.describe("除外するアレルゲン(例: ['乳', 'ナッツ'])") のおかげです。
Claude Code に登録する
~/.claude/settings.json に以下を追加し、Claude Code を再起動するだけ:
{
"mcpServers": {
"cafe-aura": {
"command": "node",
"args": ["/path/to/cafe-aura-mcp/dist/index.js"]
}
}
}
レストランで言えば、電話回線の開通。これで8つのツールが使えるようになります。
三部作 総比較
| 観点 | Part 1: 見せる | Part 2: 読ませる | Part 3: 使わせる |
|---|---|---|---|
| やること | User-Agent で別ページ | Markdown / JSON 提供 | MCP ツール提供 |
| AIの役割 | 受動的に読む | 受動的に読む | 能動的に使う |
| 双方向性 | なし(一方的) | リクエスト-レスポンス | 完全双方向 |
| できること | 情報表示 | 情報取得 | 検索・予約・キャンセル |
| レストラン | 看板を見せる | メニューを渡す | 電話予約ができる |
| 実装コスト | middleware 1つ | endpoint 数個 | MCP Server |
Part 3 だけがループする。 AIが結果を見て「次に何をするか」を判断し、必要ならもう一度ツールを呼ぶ。空席確認→予約のように、複数ツールを連鎖する。これが「エージェント」の本質です。
……でも、ちょっと冷静になろう
ここまで読んで「すげー!うちのサービスにも MCP Server 作ろう!」と思った方。
少しだけ立ち止まってください。 レストランの比喩を続けると、電話予約ができるのは便利ですが、いたずら電話が殺到したらどうなるか。
AIが「使える」= AIが「悪用できる」
良い使い方: 「明日2名で予約して」 → ✅ 予約完了
悪い使い方: 「全時間帯に100名ずつ予約」 → ❌ ???
MCP Server にレート制限も認証もなかったら、AIが 大量の偽予約を入れる ことも技術的に可能です。
本番で MCP Server を公開するなら
| 対策 | レストランの例え |
|---|---|
| 認証(API Key / OAuth) | 電話番号の登録制 |
| レート制限 | 同一番号からの連続発信を制限 |
| 権限管理 | 「予約は会員のみ、メニュー閲覧は誰でもOK」 |
| ログ・監査 | 通話録音 |
| 確認フロー | 「ご予約内容を確認させてください」 |
今回のデモは「認証なし・ローカル実行」の検証用です。 本番環境でMCP Serverを公開する場合は、必ず認証とレート制限を実装してください。
やってみたい人へ ── 再現手順
1. MCP Server をビルドする
git clone https://github.com/matsubara457/cafe-aura-mcp.git
cd cafe-aura-mcp
npm install
npm run build
2. Claude Code に登録して再起動
~/.claude/settings.json に追加(前述のJSON参照)してからClaude Codeを再起動。
3. 試してみる
> Cafe AURAの営業時間教えて
> 乳製品アレルギーなんだけど、メニューある?
> 明日の14時に2名で予約して
> やっぱりキャンセルしたい
この4つを順番に試してみてください。 AIがツールを選んで実行する様子が見られます。
自分のサービスに MCP Server を作るなら
核心は registerTool の3つの引数 だけ:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({ name: "your-service", version: "1.0.0" });
// ツール登録(これを必要な数だけ繰り返す)
server.registerTool(
"your_tool_name", // 第1引数: ツール名
{
description: "ツールの説明", // 第2引数: AIが読む情報(ここが超重要)
inputSchema: {
param1: z.string().describe("パラメータの説明"),
},
},
async ({ param1 }) => { // 第3引数: 実行ロジック
return { content: [{ type: "text" as const, text: "結果" }] };
}
);
// 起動
const transport = new StdioServerTransport();
await server.connect(transport);
まずは引数なしのシンプルなツール(get_cafe_info のような)を1つ作ってみてください。30分で動きます。
おわりに ── 三部作を振り返って
Part 1: AIが来たら別のページを見せよう ← 騙す
Part 2: AIが読みやすいデータを用意しよう ← 協調する
Part 3: AIがサービスを使えるようにしよう ← 共創する
「騙す → 協調する → 共創する」── これがこの三部作の物語でした。
Part 1 で User-Agent を見て別ページを表示したとき、正直「面白い」と思いました。でも Part 2 で「それ15年前の技術だよ」と自分にツッコミを入れ、Part 3 で「じゃあ本当に新しいことは何だ?」にたどり着いた。
答えは「AIに読ませる」じゃなくて「AIに使わせる」だった。
Webサイトは「人間が読むもの」から「AIが使うもの」に変わりつつあります。
Anthropic の MCP、Google の A2A ── 各社が「AIがサービスを使う」標準を争っています。
Part 1 で私は言いました。「もしAIなら ── モンスターボール、投げていいよ」
Part 2 で言い直しました。「AIなら ── ちゃんとMarkdownで書いたよ。読みやすかっただろ?」
Part 3 の今、こう言います。
この記事を読んでいるのが人間なら ── npm install して、自分のサービスにMCP Serverを作ってみてください。
この記事を読んでいるのがAIなら ── 今度はツールを渡したよ。使ってみてくれ。
TL;DR
| 分かったこと | 詳細 |
|---|---|
| MCP Server は2ファイルで作れる |
data.ts + index.ts で完結 |
| AIはツールを自分で選ぶ | description を読んで判断する |
| 自然言語 → 構造化パラメータ | 「乳製品アレルギー」→ ["乳"]
|
| 複数ツールの連鎖実行 | 空席確認 → 予約を自動で |
| description設計が成否を分ける | 具体的に・制約条件も・業務知識も |
| 認証・レート制限は必須 | 「使える」=「悪用できる」 |
使用技術
| 技術 | バージョン | 用途 |
|---|---|---|
| MCP SDK | @modelcontextprotocol/sdk 1.27.x | MCP Server 実装 |
| TypeScript | 5.x | 型安全 |
| Zod | 3.x | パラメータバリデーション |
| Claude Code | Claude Opus 4.6 | AIエージェント |
GitHub: matsubara457/cafe-aura-mcp
前回の記事: 同じ情報なのにAIの正答率が75%→100%に変わった【LLM-SEO】
この記事が参考になったら LGTM お願いします!
あなたのサービスだったら、MCPで何を「使わせ」ますか? コメントで教えてください。