MCP Tool Risk Contract
What this adds. SecurityRecipes now treats MCP tool metadata as risk vocabulary, not enforcement. The contract lets an agent host or MCP gateway use annotations safely while still relying on deterministic scope, authorization, sandbox, network, approval, and output controls.
MCP tools can now declare behavior with annotations such as
readOnlyHint, destructiveHint, idempotentHint, and
openWorldHint. That is valuable, but the MCP specification is clear:
clients must treat annotations as untrusted unless they come from a
trusted server. The MCP Tool Risk Contract turns that reality into a
reviewer-ready control surface. Rechecked October 7, 2026: MCP
2026-07-28
is still current and stateless. There is no negotiation handshake.
Each request carries protocol version and capabilities. Servers
MUST implement
server/discover.
--session-id and kill_session here are local run identifiers and
host-session kill switches, not Mcp-Session-Id. Streamable HTTP
revisions through
2025-11-25
could assign that header; 2026-07-28 ignores it and does not mint
session IDs. Rechecked the same day against MCP
Caching
and
Tools:
complete tools/list results MUST carry ttlMs and cacheScope.
A public cache MAY be shared across callers and access tokens,
including from an authenticated endpoint. A private cache MUST
NOT be shared across authorization contexts. cacheScope MUST
NOT replace per-primitive access control. Multi round-trip
input_required results MUST NOT be cached. Servers MUST
apply the same cacheScope to every page of a paginated list.
notifications/tools/list_changed invalidates a still-fresh cache.
This pack now denies insecure tools/list caches, kills private-cache
reuse across authorization contexts, and holds complete lists that
omit cacheScope. Unspecified cache evidence stays on the prior path.
This change does not claim human review of the pack; lastmod and the
source last_reviewed date record this editorial pass.
The core policy is simple: before a tool call runs, decide whether the session has private data, untrusted content, and an external or state-changing capability in the same execution path. If it does, the call is denied unless there is an explicit approval/control path. This makes tool risk easy for enterprise teams to reason about without pretending the model can reliably separate user instructions from attacker-controlled content.
Generated artifact
- Profile:
data/assurance/mcp-tool-risk-contract-profile.json - Generator:
scripts/generate_mcp_tool_risk_contract.py - Runtime evaluator:
scripts/evaluate_mcp_tool_risk_decision.py - Evidence pack:
data/evidence/mcp-tool-risk-contract.json - MCP tools:
recipes_mcp_tool_risk_contract, paired withrecipes_playbook_planusing playbook idmcp-tool-risk-contract.
Regenerate and validate:
python3 scripts/generate_mcp_tool_risk_contract.py
python3 scripts/generate_mcp_tool_risk_contract.py --check
Evaluate one proposed tool call:
python3 scripts/evaluate_mcp_tool_risk_decision.py \
--workflow-id vulnerable-dependency-remediation \
--namespace repo.contents \
--tool-name repo.contents.patch \
--requested-access-mode write_branch \
--agent-id sr-agent::vulnerable-dependency-remediation::codex \
--run-id run-ci \
--session-id session-ci \
--correlation-id corr-ci \
--server-trusted \
--read-only-hint false \
--destructive-hint false \
--idempotent-hint false \
--open-world-hint true \
--human-approval-id approval-ci \
--expect-decision allow_with_confirmation
Workflow at a glance
MCP Tool Risk Contract workflow
Classify an MCP tool's data access, side effects, reversibility, authority, argument controls, tools/list cacheScope, and approval needs before exposure to agents.
Signal
Describe the tool
Capture server, tool name, schema, purpose, owner, data, targets, credentials, environments, side effects, and dependencies.
Scope
Analyze capability risk
Rate read/write/admin authority, sensitive data, destructive or irreversible effects, scope, concurrency, network, and confused-deputy risk.
Decision
Define required controls
Specify argument/path validation, tenant isolation, auth, tools/list cacheScope (public vs private, not access control), rate limits, dry-run, idempotency, approvals, telemetry, and rollback.
Action
Assign an operating mode
Approve read-only, allow bounded writes, require per-call approval, restrict to pilot, hold, or deny exposure.
Proof
Publish the contract
Record schema hash, risk tier, controls, allowed workflows/identities, examples, owner, expiry, and drift triggers.
Decision gate
Are tool capabilities fully declared and controlled for their maximum data and side-effect impact?
Expose the tool only in its contracted operating mode and scope.
Hold or deny unknown, overbroad, unvalidated, destructive, or unowned capabilities. Deny public user-specific or access-control tools/list caches; kill private-cache reuse across authorization contexts.
Evidence to retain
- tool schema/capability inventory
- risk/control assessment
- operating-mode decision
Expected outputs
- MCP tool risk contract
- approval policy
- tool remediation backlog
Decision model
| Decision | Meaning |
|---|---|
allow_tool_call |
The call fits workflow scope, trusted annotations, and session-combination policy. |
allow_with_confirmation |
The call can proceed only with a durable human approval or confirmation record. |
hold_for_tool_risk_review |
Evidence is missing, annotations are untrusted for the risk level, or the tool is sensitive. |
deny_annotation_contradiction |
Runtime request contradicts the tool annotations, such as read-only metadata on a write call. |
deny_insecure_tool_list_cache |
A cached tools/list is public while user-specific, used as access control, mixed across pages, or retained from an input_required result. |
deny_session_exfiltration_path |
The session combines private data, untrusted content, and external or state-changing capability without approval. |
deny_scope_drift |
Namespace, connector, access mode, or workflow is outside the generated contract. |
kill_session_on_tool_risk_signal |
A kill signal appeared: secret-bearing arguments/results, tool-list drift after approval, private tools/list cache reuse across authorization contexts, private-network destination, or approval bypass. |
Evaluate a public cache of an identical complete tools/list:
python3 scripts/evaluate_mcp_tool_risk_decision.py \
--workflow-id vulnerable-dependency-remediation \
--namespace repo.contents \
--tool-name repo.contents.patch \
--requested-access-mode write_branch \
--agent-id sr-agent::vulnerable-dependency-remediation::codex \
--run-id run-ci \
--session-id session-ci \
--correlation-id corr-ci \
--server-trusted \
--read-only-hint false \
--destructive-hint false \
--idempotent-hint false \
--open-world-hint true \
--human-approval-id approval-ci \
--tools-list-cached \
--tools-list-result-type complete \
--tools-list-cache-scope public \
--expect-decision allow_with_confirmation
Deny a public cache of a user-specific tools/list:
python3 scripts/evaluate_mcp_tool_risk_decision.py \
--workflow-id vulnerable-dependency-remediation \
--namespace repo.contents \
--tool-name repo.contents.patch \
--requested-access-mode write_branch \
--agent-id sr-agent::vulnerable-dependency-remediation::codex \
--run-id run-ci \
--session-id session-ci \
--correlation-id corr-ci \
--server-trusted \
--read-only-hint false \
--destructive-hint false \
--idempotent-hint false \
--open-world-hint true \
--human-approval-id approval-ci \
--tools-list-cached \
--tools-list-result-type complete \
--tools-list-cache-scope public \
--tools-list-user-specific \
--expect-decision deny_insecure_tool_list_cache
What gets scored
The generator reads the MCP connector trust pack, authorization conformance pack, workflow manifest, and gateway policy. It produces a profile for every MCP namespace with:
- trusted vs untrusted annotation source
- suggested standard annotations
- risk tier
- private-data, untrusted-content, exfiltration, state-change, and approval-required factors
- authorization conformance state
- workflow-level combination risk
The pack is intentionally conservative. Open-world tools taint the
session; untrusted annotations never reduce friction for sensitive
tools; write and non-idempotent calls need approval; tool-list changes
after approval are kill signals; a public tools/list cache must not
carry user-specific tools; a private cache must not cross
authorization contexts.
Source anchors
- MCP Tools specification
- MCP Caching specification
- MCP Tool Annotations as Risk Vocabulary
- MCP Authorization specification
- MCP Security Best Practices
- MCP Elicitation specification
- OWASP Top 10 for Agentic Applications 2026
- NIST AI RMF
- NIST AI RMF Generative AI Profile