Server-Side: Guarding MCP Tools¶
This guide covers protecting MCP tools with the @guard decorator per RFC-006.
Basic Usage¶
from capiscio_mcp import guard
@guard(min_trust_level=2)
async def read_file(path: str) -> str:
"""Read a file (requires Trust Level 2+)."""
with open(path) as f:
return f.read()
GuardConfig Options¶
from capiscio_mcp import guard, GuardConfig
config = GuardConfig(
# Minimum trust level required (0-4)
min_trust_level=2,
# Accept self-signed (did:key) badges?
accept_level_zero=False,
# List of trusted issuer DIDs
trusted_issuers=[
"did:web:registry.capisc.io",
"did:web:internal.example.com",
],
# Glob patterns for allowed tool names
allowed_tools=[
"read_*",
"list_*",
],
# If True, deny anonymous/API key access
require_badge=True,
# Policy version for tracking
policy_version="v1.0",
)
@guard(config=config)
async def execute_query(sql: str) -> list[dict]:
pass
Handling Denials¶
from capiscio_mcp import guard, GuardError
@guard(min_trust_level=2)
async def sensitive_operation(data: dict) -> dict:
pass
try:
result = await sensitive_operation(data={"key": "value"})
except GuardError as e:
print(f"Access denied: {e.reason}")
print(f"Evidence ID: {e.evidence_id}") # For audit trail
print(f"Caller DID: {e.agent_did}")
Deny Reasons¶
| Reason | Meaning |
|---|---|
BADGE_MISSING | No badge provided |
BADGE_INVALID | Badge signature invalid |
BADGE_EXPIRED | Badge has expired |
BADGE_REVOKED | Badge has been revoked |
TRUST_INSUFFICIENT | Trust level too low |
TOOL_NOT_ALLOWED | Tool not in allowed_tools |
ISSUER_UNTRUSTED | Badge issuer not trusted |
POLICY_DENIED | Custom policy denied access |
Different Trust Levels for Different Tools¶
from capiscio_mcp import guard
@guard(min_trust_level=1)
async def list_files(directory: str) -> list[str]:
"""Low-risk: List files (DV sufficient)."""
pass
@guard(min_trust_level=2)
async def read_file(path: str) -> str:
"""Medium-risk: Read file contents (OV required)."""
pass
@guard(min_trust_level=3)
async def write_file(path: str, content: str) -> None:
"""High-risk: Write files (OV required)."""
pass
@guard(min_trust_level=4)
async def execute_command(cmd: str) -> str:
"""Critical: Execute shell commands (EV required)."""
pass
Context Access¶
Access caller information within guarded functions:
from capiscio_mcp import guard
from capiscio_mcp.guard import get_caller_credential
@guard(min_trust_level=2)
async def audit_operation(data: dict) -> dict:
credential = get_caller_credential()
if credential:
print(f"Caller DID: {credential.agent_did}")
print(f"Trust Level: {credential.trust_level}")
print(f"Badge JTI: {credential.badge_jti}")
return {"status": "ok"}
MCP SDK Integration¶
See MCP SDK Integration for using @guard with the official MCP SDK.