本文へ移動
🏷️

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 catalogserver / endpoint / owner / scope / review datetool 単位の annotations を catalog に取り込む
tool risk tier組織側の read / write / external action 分類MCP が返す hint を tier に写像する
approval flowsensitive tool call を実行直前に止めるどの annotation なら承認を省略できるかを決める
MCP prompt injectionresource / prompt / tool output の未信頼入力openWorldHint を未信頼入力の分類に使う
tool schemainput / 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 は次のように分けます。

toolannotation組織側の扱い
public docs searchreadOnlyHint: true, openWorldHint: true自動可。ただし出力は未信頼入力
internal schema lookupreadOnlyHint: true, openWorldHint: falsetrusted server なら自動可
customer support log searchreadOnlyHint: true, openWorldHint: true承認または専用 read-only session
GitHub issue readreadOnlyHint: true, openWorldHint: truewrite 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 を作るfalsebranch 内なら自動可
issue comment を投稿するfalseexternal action として per-call 承認
label を付けるfalserepository policy 次第で per-run 承認
ticket を close するtrueper-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 と相性が良いです。

たとえば、次のように扱います。

toolidempotentHint再実行方針
get_pull_requesttrueretry 可
create_draft_from_templatetrueoutput id を確認して retry
create_issue_commentfalseidempotency key なしでは retry 禁止
send_invoicefalseretry ではなく人間確認
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 dataT0scope と data class
readOnlyHint: true + openWorldHint: trueT0/T1prompt injection と session 分離
readOnlyHint: false + destructiveHint: falseT2/T3外部送信か、local draft か
destructiveHint: trueT4/T5rollback と owner 承認
idempotentHint: false + external actionT4retry 禁止と payload hash
annotation missingT3 以上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 を使って承認フローを残すことが推奨されています。

私なら、設定への落とし込みは次の順にします。

  1. unknown server は default approval required
  2. catalog reviewed の read-only tool だけ auto candidate
  3. openWorldHint: true は write session で auto にしない
  4. destructiveHint: true は per-call approval
  5. 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 は不要です。まずは次の順で十分です。

  1. MCP server catalog に server_trust と review_after を追加する
  2. tools/list から tool 名、description、input schema、output schema、annotations を保存する
  3. raw annotations と reviewed classification を分ける
  4. readOnlyHint + openWorldHint の tool を read-only session に閉じる
  5. destructiveHint: true と external action を per-call approval にする
  6. idempotentHint: false の tool は自動 retry しない
  7. annotation 変更を CI / 定期 job で検知する

参考資料

まとめ

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 が増えても承認境界を壊さずに運用できます。

WRITTEN BY nidoneko

Full-stack engineer with 8+ years of experience in TypeScript, React, Node.js, and cloud-native development across healthcare, finance, HR, and IoT domains.

View Profile →