MCPサーバーを構築しました。ツールは動作します。LLMエージェントに接続して動きを見ていると、ツールを無視する、おかしなパラメーターで呼び出す、どのツールを使うべきか混乱する、奇妙な順序で連鎖させる、といったことに気づきます。
これが「存在するツール」と「LLMが正しく使えるツール」の隔たりです。MCPサーバーに費やす労力の多くは、ここで無駄になります。企業は強力な機能を構築し、ツールとして公開しても、LLMがそれを効果的に使えない状況を目の当たりにします。
役立つ視点の転換は、LLMをユーザーと捉え、ツール設計をUX設計として扱うことです。ツールの説明がUI、スキーマがフォーム、エラーメッセージがフィードバックです。これらを適切に設計すれば、LLMは効果的に動作します。設計を誤れば、高度なバックエンドはエージェントから見えないも同然です。
本記事では、何が機能し、何が機能しないかを具体例とともに説明します。
原則1:ツール名で意図を伝える
ツール名はLLMが最初に目にするものです。ツールが何をするのかを、動作を表す言葉で示す必要があります。
悪い例:
customers(名詞であり、動作がない)process_customer(曖昧)do_x(意味がない)
より良い例:
search_customers(動作が明確)get_customer_by_id(具体的な操作)update_customer_email(具体的な変更)
重要な理由: LLMはツール一覧を走査して、関連するものを探します。説明的な名前なら、適切なツールをすぐに見つけられます。曖昧な名前では説明を注意深く読む必要がありますが、LLMは必ずしもそうするとは限りません。
便利なパターンは、標準的な動詞の接頭辞です。
- 読み取りには
list_*、search_*、get_*。 - 書き込みには
create_*、update_*、delete_*。 - 計算には
analyze_*、summarize_*。
サーバー全体で一貫性を持たせると、LLMがメンタルモデルを構築しやすくなります。
原則2:説明はプロンプトである
ツールの説明は、サーバー内で最も重要なテキストです。LLMはそれを基に、ツールを使うべきか、どう使うべきかを判断します。
悪い説明:
search_customers: 顧客データベースを検索します。
より良い説明:
search_customers: 名前、メールアドレス、または会社名で顧客を検索します。基本情報を含む一致顧客を最大10名返します。ユーザーが言及している顧客を特定する必要がある場合に使用してください。IDによる完全一致検索には、代わりにget_customer_by_idを使用してください。
より良い説明では、次の点に注目してください。
- 入力を説明しています(「名前、メールアドレス、または会社名で」)。
- 出力を説明しています(「基本情報を含む一致顧客を最大10名」)。
- いつ使うかを示しています(「ユーザーが言及している顧客を特定する必要がある場合」)。
- いつ使わないかを示しています(「IDによる完全一致検索には、代わりにget_customer_by_idを使用」)。
「いつ使わないか」という部分は極めて重要です。これがなければ、get_customer_by_idの方が適切な場面でも、LLMがsearch_customersを呼び出す可能性があります。
原則3:パラメーターの説明が重要である
すべてのパラメーターに説明が必要です。パラメーター名だけに頼らないでください。
悪い例:
{
customer_id: string,
fields: string[]
}
より良い例:
{
customer_id: string, // "顧客の一意な識別子。search_customersの結果またはユーザーの明示的な入力から取得します。"
fields: string[] // "返す特定のフィールド。利用可能: name、email、phone、tier、created_at、last_active。指定しない場合はnameとemailを返します。"
}
説明には次の役割があります。
- LLMに値の取得方法を伝えます。
- 該当する場合は許可される値を指定します。
- デフォルト値を示します。
原則4:エラーで復旧を導く
ツールでエラーが発生すると、エラーメッセージがLLMの次の行動を導きます。曖昧なエラーは、エージェントを混乱させます。
悪いエラー:
{ "error": "入力が無効です" }
より良いエラー:
{
"error": "validation_error",
"message": "メールアドレス「...」の形式が正しくありません。「name@example.com」のような形式にする必要があります。",
"field": "email",
"suggestion": "有効なメールアドレスをユーザーに確認してください。"
}
これでLLMは次のことを理解できます。
- 何が問題だったか(emailフィールドの検証エラー)。
- どう修正するか(有効なメールアドレス形式を使う)。
- 次に何をするか(ユーザーに確認する)。
2つのエラーを受けたエージェントの挙動を比べてみましょう。最初のエラーでは、同じ呼び出しを再試行する(無駄)、諦める(悪いUX)、または有効な入力を捏造する可能性があります。2つ目なら、ユーザーとのやり取りを円滑に進められます。
原則5:出力が次の行動を形作る
ツールの出力によって、LLMが次に何をするかが決まります。出力設計はエージェントの挙動に影響します。
検索出力の悪い例:
[
{"id": "c1", "n": "John", "e": "john@..."},
{"id": "c2", "n": "Jane", "e": "jane@..."}
]
より良い出力:
{
"customers": [
{"id": "c1", "name": "John Smith", "email": "john@example.com", "tier": "pro"},
{"id": "c2", "name": "Jane Doe", "email": "jane@example.com", "tier": "free"}
],
"total_found": 2,
"summary": "「john」に一致する顧客が2名見つかりました。うち1人の名前は「Jane Doe」ですが、メールアドレスに「john」が含まれています。"
}
より良い出力には、次の特徴があります。
- 読みやすいフィールド名を使っています。
- メタコンテキスト(
total_found)を含みます。 - LLMが次に何を言うかを組み立てるのに役立つ、自然言語の
summaryを含みます。
summaryフィールドには大きな効果があります。結果をどう解釈すべきかについて、LLMに「ちなみに」とヒントを与えるようなものです。
原則6:1ツール、1機能
複数のことを行うツールはLLMを混乱させます。LLMは、ツールを使うべきかだけでなく、どのモードを使うかも判断しなければなりません。
分かりにくい例:
manage_customer:
- mode: "search" | "get" | "update" | "delete"
- params: モードにより異なる
LLMはモードを選ばなければなりませんが、誤ったモードを選ぶことがよくあります。さらに悪いことに、パラメータースキーマがモードごとに変わるため複雑になります。
より良い例:ツールを分けます。
search_customers: 名前、メールアドレス、会社名で検索
get_customer: IDで詳細を取得
update_customer: 特定のフィールドを更新
delete_customer: 顧客をアーカイブ
各ツールの意味が明確になります。LLMは意図に基づいて1つを選びます。スキーマも単純です。
ツール数は増えますが、個々のツールは明確になります。LLMは、3個のマルチモードツールよりも10個の明確なツールを適切に扱えます。
原則7:入力を制約する
可能な場合は、入力の選択肢を制限してください。enumと検証により、LLMのハルシネーションを防げます。
制約が緩い例:
{
status: string // 何でも指定できる
}
制約した例:
{
status: "active" | "trial" | "churned" | "suspended"
}
制約はスキーマレベルで適用されます(制約付き生成により、LLMが無効な値を生成するのを防ぎます)。
操作、重大度、種類など、有効な値の集合が既知であるものには、同じ考え方が当てはまります。
日付にはISO 8601形式を使い、説明にも明記してください(「ISO 8601形式の日付。例:2026-05-15」)。明記しないと、LLMはさまざまな形式の日付を生成します。
原則8:デフォルト値でハルシネーションを減らす
適切なデフォルト値があるパラメーターは省略可能にし、サーバー側でデフォルトを適用してください。
悪い例:
{
query: string,
limit: number, // LLMが何らかの値を指定する必要がある
include_archived: boolean,
sort_by: string
}
LLMはこれらすべての値を選ぶ必要があり、誤る可能性があります。
より良い例:
{
query: string,
limit: number = 10, // 妥当なデフォルト
include_archived: boolean = false, // 安全なデフォルト
sort_by: "relevance" | "name" | "created_at" = "relevance" // 最も一般的
}
LLMは、特定のクエリに関係するパラメーターだけを指定します。パラメーターが少ないほど、混乱の余地も減ります。
デフォルト値は説明に明記してください。「Limit: 返す結果の件数。デフォルトは10、最大50。」
原則9:構成可能性が重要である
ツールは、LLMが組み立てられるワークフローを構成できる必要があります。適切な粒度なら、複雑なタスクも簡単になります。
「上位3社の顧客について、未解決の問題をすべて教えてください」というタスクを考えてみましょう。
悪いツールセット:
get_customer_summary(customer_id): 顧客、チケット、アクティビティーをすべてまとめて返す
LLMは「上位3社」という絞り込みを簡単には行えません。このツールは一度に1社の顧客情報をすべて返すからです。タスクを実行するには、まず上位顧客を把握してから、このツールを3回呼び出す必要があります。
より良いツールセット:
list_customers(sort_by="value", limit=N): 優先度情報を含む顧客の概要を返す
list_tickets(customer_id, status): 顧客のチケットを返す
LLMは自然に構成できます。まず上位顧客を一覧表示し、それぞれについて未解決のチケットを一覧表示します。
原則は、複数ツールによるワークフローを考えることです。うまく構成できるツールは使いやすく、構成できないツールは使いにくいことが多くなります。
原則10:冪等性を伝える
書き込みツールでは、冪等性の要件を説明に記載します。
create_invoice: 顧客向けの新しい請求書を作成します。
重要: idempotency_key(生成したUUID)を渡してください。この操作を再試行する場合は、請求書の重複を防ぐため、同じUUIDを使用してください。
パラメーター:
- amount: ...
- customer_id: ...
- idempotency_key: 再試行時の重複作成を防ぐUUID。論理操作ごとに一度だけ生成します。
これでLLMは、UUIDを生成し、再試行時には同じものを使う必要があると理解します。
この指示がなければ、LLMはキーを省略する(冪等性がない)か、再試行のたびに新しいUUIDを生成する(目的を損なう)可能性があります。
原則11:事前条件と事後条件を明記する
事前条件や重要な副作用があるツールでは、それを明記します。
delete_customer: 顧客レコードをアーカイブします。30日以内であれば元に戻せます。30日後、データは完全に削除されます。
事前条件:
- 顧客に有効なサブスクリプションがないこと。
- 顧客に未解決のチケットがないこと。
事前条件を満たさない場合、このツールは最初に解決すべき内容を示すエラーを返します。
副作用:
- 顧客の連絡先もすべてアーカイブされます。
- 顧客は有効なレポートから除外されます。
- 監査ログエントリが作成されます。
これでLLMは、呼び出す前に何を確認すべきか、呼び出した後に何が起きるかを理解します。「まずチケットをクローズしてから削除する」といった複数ステップのワークフローを正しく計画できます。
原則12:迷ったら例を示す
複雑なツールでは、説明に例を含めると役立ちます。
analyze_funnel: イベントデータからコンバージョンファネルを分析します。
パラメーター:
- start_date: ISO 8601形式の日付
- end_date: ISO 8601形式の日付
- steps: ステップ定義の配列。各要素は{event_name: string, filters?: object}
例:
{
"start_date": "2026-01-01",
"end_date": "2026-01-31",
"steps": [
{"event_name": "signup"},
{"event_name": "first_login"},
{"event_name": "first_action", "filters": {"action_type": "create_project"}},
{"event_name": "subscription_started"}
]
}
例を使うと、スキーマだけの場合よりも構造をLLMにうまく伝えられます。
原則13:内部実装を公開しない
LLMがデータベース構造や内部IDを知る必要はありません。すっきりした概念モデルを公開してください。
悪い例:
get_user_by_pk(pk: number)
LLMは、データベースの概念である「主キー」を使う必要があると理解しなければなりません。
より良い例:
get_user(user_id: string)
データベースの概念を隠します。LLMは意味のある概念であるuser_idを使います。
同様に、非推奨のフィールド、内部フラグ、デバッグパラメーターなど、ユーザー向けの概念ではなく実装に関するものを公開しないでください。
原則14:マジック文字列を避ける
コマンドやコードのような文字列を必要とするツールがありますが、これは間違いが起きやすい設計です。
悪い例:
modify_record(record_id: string, change_string: string)
// change_stringは"field1=value1;field2=value2"のような形式
LLMは変更内容を特定の文字列形式にエンコードする必要があり、間違いを起こします。
より良い例:
update_record(record_id: string, updates: { field1?: any; field2?: any; ... })
変更内容をオブジェクトとして構造化します。LLMは任意のフィールドを直接使えます。
原則15:実際のLLMでテストする
ツールの説明が人間には分かりやすくても、LLMには分かりにくい可能性があります。確認する唯一の方法は、テストすることです。
有用なワークフローは次のとおりです。
- ツールを構築します。
- LLMエージェントに、ツールだけを使って現実的なタスクをいくつか試行させます。
- 失敗を観察します。
- 失敗に基づいて説明を調整します。
- 繰り返します。
次のようなパターンが見つかります。
- LLMが誤ったツールを使う → ツール名または説明が不明確です。
- LLMが誤ったパラメーター値を渡す → パラメーターの説明またはスキーマを改善する必要があります。
- LLMがエラー後に諦める → エラーメッセージを改善する必要があります。
- 役立つツールをLLMが試さない → ツールが適切に提示されていないか、名前が分かりにくくなっています。
各問題から、具体的な修正方法が分かります。
診断:ツール設計が不十分である兆候
ツール設計に問題があることを示すパターンをいくつか挙げます。
LLMが誤ったツールを頻繁に使う。 get_customer_by_idを呼ぶべき場面でsearch_customersを呼ぶ様子が見られます。修正方法:どの状況でどのツールを使うかを明確にします。
LLMが1つのことを行うために多数のツールを呼び出す。 本来なら1回で済む処理のために5回のツール呼び出しを連鎖させます。修正方法:より高レベルの複合ツールが必要か、粒度が細かすぎる可能性があります。
LLMがエラー後に諦める。 一度試してエラーになると、ユーザーに「対応できません」と伝えます。修正方法:次の手順を提案する、より良いエラーメッセージを用意します。
LLMがパラメーター値を捏造する。 user_id、日付、IDを作り出します。修正方法:有効な値の取得方法を明確にし、制約を加え、問題を検出して説明するエラー処理を追加します。
LLMが同じ失敗した呼び出しを繰り返す。 同じエラーを繰り返します。修正方法:エラーメッセージが具体的な問題をLLMに伝えられていません。
LLMが強力なツールを使わない。 優れたツールを構築しても、LLMがまったく呼び出しません。修正方法:発見しやすさを改善します(より明確な名前、より良い説明、「この場合に使用する」という指示)。
ツールの分類体系
ツールを分類体系に整理することは、有用な演習です。
読み取りツール(安全、冪等):
- search_customers
- get_customer_by_id
- list_tickets
- list_orders
計算ツール(状態変更なし):
- summarize_account_activity
- analyze_funnel
- calculate_lifetime_value
書き込みツール(状態変更あり、冪等性が必要):
- create_customer
- update_customer_email
- create_ticket
- send_email
破壊的ツール(慎重な認可が必要):
- delete_customer
- cancel_subscription
- archive_record
分類体系には、次の利点があります。
- 適切なガードレールを適用できます(冪等性、破壊的操作の確認)。
- LLM向けのシステムプロンプトに分類を記載できます。
- 不足しているツールを発見できます(空の分類がある場合、ツールが必要ではないでしょうか)。
システムプロンプトに追加すると役立つ内容は次のとおりです。
利用可能なツール分類:
- READツール(安全に呼び出せる): search_customers、get_customer_by_id、...
- COMPUTEツール(副作用なし): summarize_account_activity、...
- WRITEツール(副作用あり、idempotency_keyを含める): create_customer、...
- DESTRUCTIVEツール(人間による確認が必要): delete_customer、...
WRITEまたはDESTRUCTIVEツールを呼び出す前に、ユーザーに確認してください。
これは個々の呼び出しだけでなく、ワークフローのレベルでLLMによるツールの使い方を形作ります。
よくある改善例
原則を具体的にするため、改善前と改善後の例を示します。
例1:検索ツール
改善前:
// ドキュメントを検索
{
name: "documents",
description: "ドキュメントを検索",
inputSchema: { query: "string" }
}
改善後:
{
name: "search_documents",
description: `社内ドキュメント(ナレッジベース、Wikiページ、ポリシー)を検索します。
タイトル、抜粋、リンクを含む一致ドキュメントを返します。ユーザーが社内のポリシー、手順、または社内資料について質問した場合に使用してください。意味的類似度が最も高い一致結果を最大10件返します。`,
inputSchema: {
query: {
type: "string",
description: "検索クエリ。具体的に指定してください。良い例: 「2026年リモートワークポリシー」。悪い例: 「仕事に関するドキュメント」。"
},
document_type: {
type: "string",
enum: ["policy", "procedure", "guide", "faq", "any"],
default: "any",
description: "特定のドキュメント種別に絞り込みます。"
},
limit: {
type: "number",
default: 5,
maximum: 10,
description: "結果の件数。"
}
}
}
例2:アクションツール
改善前:
{
name: "send_email",
description: "メールを送信",
inputSchema: {
to: "string",
subject: "string",
body: "string"
}
}
改善後:
{
name: "draft_email_to_customer",
description: `最近のやり取りに基づいて顧客宛てのメールを下書きします。メールは送信前の人間による確認用に下書きとして保存され、自動送信はされません。ユーザーは受信トレイで下書きを承認する必要があります。
使用する場合:
- 顧客へのフォローアップが必要なアクションを特定済みである。
- メールを送る具体的な理由と内容がある。
使用しない場合:
- マーケティングまたは販促コンテンツを送る場合。
- ユーザーから明示的な依頼がない場合。
- 返金またはキャンセル依頼に返信する場合(代わりに人間へエスカレーションしてください)。`,
inputSchema: {
customer_id: {
type: "string",
description: "search_customersまたはget_customerで取得した顧客ID。"
},
subject: {
type: "string",
description: "メールの件名。4~8語で具体的にします。「ご連絡」のような一般的な件名は避けてください。"
},
body: {
type: "string",
description: "メール本文。プレーンテキストで3~5文。個別的かつ具体的にし、テンプレート的な表現は避けてください。"
},
tone: {
type: "string",
enum: ["professional", "friendly", "apologetic", "urgent"],
default: "professional",
description: "メールのトーン。"
},
idempotency_key: {
type: "string",
description: "この下書き用のUUID。重複を避けるため、再試行時には同じUUIDを使用してください。"
}
}
}
「改善後」のバージョンは、はるかに効果的にLLMを導きます。冗長に感じられますが、それだけの価値があります。
要点
LLM向けのツール設計は、それ自体が1つの専門分野です。原則は直感的なものではなく、LLMをユーザーとして捉え、それに応じてインターフェースを設計する必要があります。
重要なパターンは次のとおりです。
- 動作を示す動詞を使った名前。
- 何をするか、いつ使うか、いつ使わないかを説明する詳しい記述。
- 例と制約を含むパラメーターごとの説明。
- 構造化された実行可能なエラーメッセージ。
- 次の行動を導く出力形式。
- 1つの概念につき1つのツール。
- 妥当なデフォルト値。
- 組み合わせやすい粒度。
- 冪等性の明示。
- 事前条件と事後条件の文書化。
- 複雑なツールの例。
- 内部実装の非公開。
- 実際のLLMによるテスト。
ほとんどのMCPサーバーが失敗するのは、プロトコルが難しいからではなく、LLMを念頭に置いてツールが設計されていないからです。ツール設計が適切ならサーバーは効果を発揮します。設計を誤れば、高度なバックエンドが無駄になります。
LLMをユーザーとして扱い、それに合わせて設計してください。その投資は、ツールが実際にどれほど適切に使われるかという点で、何倍もの成果をもたらします。



