MCP server を組織で使い始めると、最初は「どの server を許可するか」に議論が寄ります。けれど実務で詰まるのは server 名だけではありません。同じ server の中に、検索だけの tool、コメントを書く tool、外部 API を更新する tool、再実行すると二重送信になる tool が混ざります。
そこで見落とされやすいのが MCP tool annotations です。readOnlyHint、destructiveHint、idempotentHint、openWorldHint は、MCP tool がどんな性質を持つかを伝えるリスク語彙です。ただし、これはポリシーそのものではありません。MCP 仕様でも、annotations は trusted server 由来でなければ信頼できないものとして扱うべきだとされています。
この記事では、MCP tool annotations を server 側の自己申告 として受け取り、組織側の catalog、risk tier、承認フローへどう接続するかを整理します。MCP server の許可台帳は MCP サーバーカタログ設計ガイド、tool の危険度分類は AIエージェント tool risk tier 設計ガイド、実行直前の human-in-the-loop は AIエージェント承認フロー設計ガイド、未信頼入力は MCP prompt injection 対策ガイド に分けています。
今回はその間にある「MCP server が出す hint を、どこまで自動承認に使ってよいか」だけを扱います。
先に結論: annotations は許可ではなく審査材料にする
私なら、MCP tool annotations は次のように扱います。
| annotation | 何を示すか | 使い方 |
|---|---|---|
readOnlyHint | 環境を変更しないか | read-only 候補として扱う |
destructiveHint | 変更が破壊的か | per-call 承認や禁止の候補にする |
idempotentHint | 同じ引数で再実行しても安全か | retry / timeout 後の扱いに使う |
openWorldHint | 外部世界とやり取りするか | 未信頼入力・外部送信の境界に使う |
重要なのは、annotation をそのまま allow にしないことです。
readOnlyHint: true が付いていても、server 自体が未審査なら信用しません。destructiveHint: false でも、外部サービスへ投稿するなら人間承認に戻します。idempotentHint: true でも、監査ログや通知の副作用があるなら再実行条件を確認します。openWorldHint: false でも、実際の endpoint や認証 scope が広いなら catalog で落とします。
annotations は「便利な分類ラベル」ではなく、「server がそう主張している」という審査材料です。
既存記事との役割分担
このテーマは近い記事が多いので、先に役割を切ります。
| 近い記事 | 扱うこと | この記事で扱うこと |
|---|---|---|
| MCP server catalog | server / endpoint / owner / scope / review date | tool 単位の annotations を catalog に取り込む |
| tool risk tier | 組織側の read / write / external action 分類 | MCP が返す hint を tier に写像する |
| approval flow | sensitive tool call を実行直前に止める | どの annotation なら承認を省略できるかを決める |
| MCP prompt injection | resource / prompt / tool output の未信頼入力 | openWorldHint を未信頼入力の分類に使う |
| tool schema | input / output / error / version の操作契約 | behavior hints を schema と別枠で扱う |
つまり、tool annotations は governance の中心ではありません。中心は自社の policy です。ただ、MCP server が増えたときに、tool ごとの性質を毎回文章だけで読むのはつらい。そこで annotations を catalog と policy の入力として使います。
MCP 仕様で annotations はどう位置づけられているか
MCP の Tools 仕様では、tool definition は name、description、inputSchema、outputSchema などに加えて、tool behavior を説明する optional な annotations を持てます。
同じ仕様では、trust and safety の観点から human-in-the-loop を残し、どの tool が AI に exposed されているかを明示し、tool invocation を可視化し、操作には confirmation prompt を提示することが推奨されています。つまり MCP は、model が tool を自動発見・自動呼び出しできる前提を持ちつつ、実装側に承認境界を置く設計です。
さらに重要なのは、tool annotations は trusted server 由来でなければ信頼できない、という点です。ここを読み飛ばすと、readOnlyHint: true と書いた unknown server の tool を自動許可してしまいます。
MCP blog の tool annotations 解説では、現在の ToolAnnotations は title と4つの boolean hints を持ち、未指定時の default は保守的に扱う前提だと整理されています。特に openWorldHint は preflight だけでなく、tool call 後に戻ってくる content の信頼性にも関わります。
私の実務解釈は単純です。
- trusted server の annotation: catalog と policy の入力にできる
- unknown server の annotation: human review の材料にだけ使う
- annotation がない tool: safe とは見なさず、pessimistic default で扱う
- annotation と実際の挙動が違う tool: server catalog から外す
readOnlyHint は便利だが、安全判定ではない
一番使いやすいのは readOnlyHint です。
docs search、repo metadata、issue list、CI log の読み取りなどは、readOnlyHint: true で表現しやすい。承認疲れを減らすためにも、read-only tool は自動許可候補にしたいところです。
ただし、read-only には2つの落とし穴があります。
1つ目は、読むだけでも機密情報に触れることです。customer data、private repo、社内 docs、incident log を読むなら、環境を変更しなくても低リスクではありません。
2つ目は、読むだけでも prompt injection を持ち込むことです。issue 本文、外部 web、チャット、メール、support log には、モデルへの命令に見える文字列が混ざります。readOnlyHint: true は write しないという意味であって、内容が信頼できるという意味ではありません。
私なら readOnlyHint は次のように分けます。
| tool | annotation | 組織側の扱い |
|---|---|---|
| public docs search | readOnlyHint: true, openWorldHint: true | 自動可。ただし出力は未信頼入力 |
| internal schema lookup | readOnlyHint: true, openWorldHint: false | trusted server なら自動可 |
| customer support log search | readOnlyHint: true, openWorldHint: true | 承認または専用 read-only session |
| GitHub issue read | readOnlyHint: true, openWorldHint: true | write tool と同じ session に置かない |
readOnlyHint は「変更しない」だけです。「漏れない」「誘導されない」「外部影響がない」までは含みません。
destructiveHint は write の中を分ける
write tool を全部同じ扱いにすると、承認が荒くなります。
たとえば、PR comment を投稿する、label を付ける、ticket を作る、feature branch に file を書く、production data を削除する。全部 write ですが、同じではありません。
destructiveHint は、変更が破壊的かどうかを示す hint です。これを使うと、write tool を少なくとも3段階に分けられます。
| 操作 | destructiveHint | 承認 |
|---|---|---|
| draft PR body を作る | false | branch 内なら自動可 |
| issue comment を投稿する | false | external action として per-call 承認 |
| label を付ける | false | repository policy 次第で per-run 承認 |
| ticket を close する | true | per-call 承認 |
| data を delete する | true | 原則禁止または owner 承認 |
ここで大事なのは、destructiveHint: false を「承認不要」と読まないことです。破壊的ではなくても、外部に残る操作は外部副作用です。PR comment、Slack 投稿、ticket 作成、メール送信は、消せるとしても組織の外部状態を変えます。
私なら destructiveHint は、禁止や per-call 承認を強める方向に使います。承認を弱める方向に使うのは、trusted server で、対象 scope が catalog と一致し、audit log が取れている場合だけです。
idempotentHint は retry のために使う
AIエージェント運用では、timeout、network error、approval 待ち、agent の再実行が必ず起きます。そのときに怖いのが二重実行です。
idempotentHint は、同じ arguments で再実行しても追加の副作用がないかを示す hint です。これは承認よりも、retry / checkpoint / run control と相性が良いです。
たとえば、次のように扱います。
| tool | idempotentHint | 再実行方針 |
|---|---|---|
get_pull_request | true | retry 可 |
create_draft_from_template | true | output id を確認して retry |
create_issue_comment | false | idempotency key なしでは retry 禁止 |
send_invoice | false | retry ではなく人間確認 |
deploy_release | 状況次第 | release id / target env を確認してから判断 |
実務では、idempotentHint: true だけでは足りません。業務側の idempotency key、external id、request id、dedupe key が必要です。
mcp_tool_policy:
tool: github.create_issue_comment
annotations:
readOnlyHint: false
destructiveHint: false
idempotentHint: false
openWorldHint: true
retry_policy:
retry_automatically: false
require:
- approval_id
- target_issue
- payload_hash
- previous_tool_result
idempotentHint は retry の許可ではなく、retry 設計を始めるための材料です。
openWorldHint は tool の外部性を見る
openWorldHint は少し分かりにくいですが、実務では一番重要です。
閉じた世界の tool は、固定された schema、社内で管理された data、local sandbox だけを扱います。開いた世界の tool は、外部 web、SaaS、ユーザー生成コンテンツ、第三者 API、変化する remote system とつながります。
openWorldHint: true の tool は、次のどちらか、または両方のリスクを持ちます。
- 外部から未信頼入力を持ち込む
- 外部へ data や action を出す
この2つを混ぜると危険です。GitHub issue を読む tool と、GitHub comment を書く tool を同じ session で自動許可すると、issue 本文の injection が comment 投稿へつながります。
私なら、openWorldHint は session 分離の判断に使います。
session_policy:
external_research:
allowed_annotations:
readOnlyHint: true
openWorldHint: true
deny_tools:
- write
- deploy
- send_message
- create_comment
controlled_write:
allowed_annotations:
openWorldHint: false
require:
- reviewed_context_packet
- approval_id
- target_scope
open world な read tool は便利です。ただし、write tool と同居させるなら human approval を残します。
catalog には raw annotations と reviewed classification を両方残す
MCP server catalog に tool annotations を取り込むとき、raw 値だけを保存すると危険です。
悪い例:
tools:
search_docs:
readOnlyHint: true
これでは、誰がその hint を確認したのか、組織側ではどう分類したのか、いつ再審査するのかが分かりません。
良い例:
mcp_tool_catalog:
server_id: docs-search-mcp
server_trust: internal_managed
reviewed_at: 2026-08-11
reviewer: platform-security
tools:
search_docs:
raw_annotations:
readOnlyHint: true
destructiveHint: false
idempotentHint: true
openWorldHint: true
reviewed_classification:
risk_tier: T0
input_trust: external_text
approval: auto_in_read_only_session
write_session_allowed: false
evidence:
- tools/list snapshot hash
- source repository tag
- test result
review_after: 2026-09-11
raw annotation は server の主張です。reviewed classification は組織の判断です。この2つを分けると、server update で annotations が変わったときに再審査できます。
tool risk tier へ写像する
annotations をそのまま tier に変換するのは危険ですが、初期分類には使えます。
私なら、まず次の mapping から始めます。
| 条件 | 初期 tier | 追加確認 |
|---|---|---|
readOnlyHint: true + trusted server + closed data | T0 | scope と data class |
readOnlyHint: true + openWorldHint: true | T0/T1 | prompt injection と session 分離 |
readOnlyHint: false + destructiveHint: false | T2/T3 | 外部送信か、local draft か |
destructiveHint: true | T4/T5 | rollback と owner 承認 |
idempotentHint: false + external action | T4 | retry 禁止と payload hash |
| annotation missing | T3 以上 | server owner に補正要求 |
この mapping は自動許可のためではなく、レビュー漏れを減らすための初期値です。最終的な tier は、target、scope、data class、external action、rollback、audit log で決めます。
Claude Code と OpenAI 側の承認設定に落とす
MCP tool annotations は、実際の client / agent runtime の承認機構とつなげて初めて効きます。
Claude Code では、MCP tool を mcp__<server>__<tool> のような tool 名として permission rules に載せられます。deny / ask / allow を server や tool 単位で設計できるため、catalog の reviewed classification を設定へ落としやすいです。Claude Agent SDK では permission evaluation の中で hooks、deny、ask、permission mode、allow、canUseTool callback の順に判定できます。さらに MCP tool が user interaction を要求する annotation を持つ場合、allow rule があっても callback へ戻る経路があります。
OpenAI の Responses API / Agents の MCP 連携では、remote MCP tool call に approval request を挟めます。公式ガイドでは、remote MCP server は外部サービスへの read / send / action を可能にするため、sensitive action には require_approval と allowed_tools を使って承認フローを残すことが推奨されています。
私なら、設定への落とし込みは次の順にします。
- unknown server は default approval required
- catalog reviewed の read-only tool だけ auto candidate
openWorldHint: trueは write session で auto にしないdestructiveHint: trueは per-call approval- annotation missing は owner に補正依頼し、それまでは ask
設定例としては、まずすべての MCP tool を ask に寄せ、catalog で確認済みの tool だけを narrow allow にします。
{
"permissions": {
"ask": ["mcp__*"],
"allow": [
"mcp__docs_search__search_docs",
"mcp__repo_meta__get_pull_request"
],
"deny": ["mcp__github__merge_pull_request", "mcp__billing__*"]
}
}
ここで mcp__* を一括 allow しないのがポイントです。server 単位の信頼と tool 単位の承認を混ぜないようにします。
CI で tools/list を検査する
MCP tool annotations は、導入時だけ見ても足りません。server update で tool が増える、description が変わる、annotation が外れる、openWorldHint が変わる、ということが起きます。
私なら、MCP server を組織 catalog に入れたら、CI か定期 job で tools/list の snapshot を検査します。
mcp_tool_annotation_check:
server_id: github-readonly-mcp
expected_tools:
get_pull_request:
readOnlyHint: true
destructiveHint: false
openWorldHint: true
create_issue_comment:
readOnlyHint: false
destructiveHint: false
openWorldHint: true
approval: per_call
fail_when:
- new_tool_without_review
- annotation_removed
- readOnlyHint_changed_to_false
- destructiveHint_changed_to_true
- output_schema_removed
この検査は security scan というより、運用 drift の検知です。MCP server が悪意なく更新されても、agent 側の承認境界は壊れます。catalog に tool_list_hash と review_after を持たせ、差分が出たら再審査へ戻します。
よくある失敗
readOnlyHint を auto-approve に直結する
read-only は変更しないという意味であって、機密性や未信頼入力の問題を消しません。customer data や外部 issue を読むなら、別の制御が必要です。
server allowlist と tool annotations を混ぜる
server が trusted でも、すべての tool が同じ risk とは限りません。server catalog には server 単位の審査、tool catalog には tool 単位の annotations と classification を置きます。
annotation missing を低リスク扱いする
annotation がない tool は、情報不足です。便利だから通すのではなく、owner に補正させるか、pessimistic default で ask / deny に寄せます。
destructiveHint: false を安全扱いする
破壊的でない外部 action は普通にあります。comment、ticket、message、notification、CRM update は、削除ではなくても外部状態を変えます。
openWorldHint を preflight だけで見る
open world な tool は、呼び出し前だけでなく、戻ってきた content の扱いも変えます。tool output をそのまま次の write に使わず、要約、引用、reviewed context packet に落とします。
最小導入チェックリスト
最初から大きな governance system は不要です。まずは次の順で十分です。
- MCP server catalog に
server_trustとreview_afterを追加する tools/listから tool 名、description、input schema、output schema、annotations を保存する- raw annotations と reviewed classification を分ける
readOnlyHint+openWorldHintの tool を read-only session に閉じるdestructiveHint: trueと external action を per-call approval にするidempotentHint: falseの tool は自動 retry しない- annotation 変更を CI / 定期 job で検知する
参考資料
- MCP Tools specification
- MCP blog: Tool Annotations as Risk Vocabulary
- OpenAI MCP and Connectors guide
- Claude Code permissions
- Claude Agent SDK permissions
まとめ
MCP tool annotations は、AIエージェント運用で使える良いリスク語彙です。readOnlyHint、destructiveHint、idempotentHint、openWorldHint が揃うと、tool の性質を catalog、approval、retry、session 分離へつなげやすくなります。
ただし、annotations は policy ではありません。server が出す自己申告を、trusted server、scope、data class、external action、audit log、reviewed classification と照合して初めて実務で使えます。
まずは tools/list の snapshot を取り、raw annotations と組織側の risk_tier を別々に保存する。そこから read-only session、per-call approval、retry 禁止、annotation drift 検知へ落とす。この順番なら、MCP server が増えても承認境界を壊さずに運用できます。