Tool-level security for Model Context Protocol servers.
MCP Guard provides trust badges and identity verification for Model Context Protocol (MCP) tool calls. It implements:
- RFC-006: MCP Tool Authority and Evidence
- RFC-007: MCP Server Identity Disclosure and Verification
pip install capiscio-mcpFor MCP SDK integration (FastMCP wrapper):
pip install capiscio-mcp[mcp]MCP servers expose powerful tools to autonomous agents—file systems, databases, APIs. But MCP itself doesn't define how to:
- Authenticate which agent is calling a tool
- Authorize whether that agent should have access
- Audit what happened for post-incident review
MCP Guard solves this with:
| Feature | Description |
|---|---|
| @guard decorator | Protect tools with trust-level requirements |
| Evidence logging | Cryptographic audit trail for every invocation |
| Server identity | Verify MCP servers before connecting |
| Server registration | Generate keypairs and register server DIDs |
| Trust levels | 0 (self-signed) → 4 (extended validation) |
Protect your MCP tools with trust-level requirements:
from capiscio_mcp import guard
@guard(min_trust_level=2)
async def read_database(query: str) -> list[dict]:
"""Only agents with Trust Level 2+ can execute this tool."""
# ... database query logic
pass
# Sync version available
from capiscio_mcp import guard_sync
@guard_sync(min_trust_level=2)
def read_database_sync(query: str) -> list[dict]:
passfrom capiscio_mcp import guard, GuardConfig
config = GuardConfig(
min_trust_level=2,
trusted_issuers=["did:web:registry.capisc.io"],
allowed_tools=["read_*", "list_*"],
require_badge=True, # Deny anonymous access
)
@guard(config=config)
async def execute_query(sql: str) -> list[dict]:
passVerify the identity of MCP servers you connect to:
from capiscio_mcp import verify_server, ServerState
result = await verify_server(
server_did="did:web:mcp.example.com",
server_badge="eyJhbGc...",
transport_origin="https://mcp.example.com",
)
if result.state == ServerState.VERIFIED_PRINCIPAL:
print(f"Trusted server at Level {result.trust_level}")
elif result.state == ServerState.DECLARED_PRINCIPAL:
print("Server identity declared but not verified")
elif result.state == ServerState.UNVERIFIED_ORIGIN:
print("Warning: Server did not disclose identity")Register your MCP server's identity with the CapiscIO registry:
from capiscio_mcp import setup_server_identity
# One-step setup: generate keys + register with registry
result = await setup_server_identity(
server_id="550e8400-e29b-41d4-a716-446655440000", # From dashboard
api_key="sk_live_...", # Registry API key
ca_url="https://registry.capisc.io", # Optional, defaults to production
output_dir="./keys",
)
print(f"Server DID: {result['did']}")
# did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK
print(f"Private key saved to: {result['private_key_path']}")from capiscio_mcp import generate_server_keypair, register_server_identity
# Step 1: Generate keypair
keys = await generate_server_keypair(output_dir="./keys")
# Step 2: Register with registry
await register_server_identity(
server_id="550e8400-e29b-41d4-a716-446655440000",
api_key="sk_live_...",
did=keys["did_key"],
public_key=keys["public_key_pem"],
ca_url="https://registry.capisc.io", # Optional, defaults to production
)For seamless integration with the official MCP Python SDK, install with the mcp extra:
pip install capiscio-mcp[mcp]Create an MCP server with built-in trust enforcement:
from capiscio_mcp.integrations.mcp import CapiscioMCPServer
server = CapiscioMCPServer(
name="filesystem",
did="did:web:mcp.example.com:servers:filesystem",
badge="eyJhbGc...", # From CapiscIO registry
)
@server.tool(min_trust_level=2)
async def read_file(path: str) -> str:
"""Only agents with Trust Level 2+ can read files."""
with open(path) as f:
return f.read()
@server.tool(min_trust_level=0)
async def list_files(directory: str) -> list[str]:
"""Any authenticated agent can list files."""
import os
return os.listdir(directory)
# Run the server (stdio transport)
server.run()Connect to MCP servers via stdio transport:
from capiscio_mcp.integrations.mcp import CapiscioMCPClient
async with CapiscioMCPClient(
command="python",
args=["my_mcp_server.py"],
min_trust_level=1,
badge="eyJhbGc...", # Your client badge
) as client:
# List available tools
tools = await client.list_tools()
print(f"Available tools: {[t['name'] for t in tools]}")
# Call a tool
result = await client.call_tool("read_file", {"path": "/data/config.json"})
print(result)MCP Guard connects to capiscio-core for cryptographic operations:
SDK automatically downloads and manages the core binary:
pip install capiscio-mcp
# Just works! Binary downloaded on first use.Connect to a separately managed core service:
# Start core in another terminal
capiscio mcp serve --listen localhost:50051
# SDK connects to external core
export CAPISCIO_CORE_ADDR="localhost:50051"Per RFC-002 v1.4:
| Level | Name | Validation | Use Case |
|---|---|---|---|
| 0 | Self-Signed (SS) | None, did:key issuer |
Local dev, testing, demos |
| 1 | Registered (REG) | Account registration | Development, internal agents |
| 2 | Domain Validated (DV) | DNS/HTTP challenge | Production, B2B agents |
| 3 | Organization Validated (OV) | DUNS/legal entity | High-trust production |
| 4 | Extended Validated (EV) | Manual review + legal | Regulated industries |
Every tool invocation—allowed or denied—produces an evidence record:
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:
# Evidence logged even on denial
print(f"Denied: {e.reason}")
print(f"Evidence ID: {e.evidence_id}") # For audit trailEvidence includes:
- Tool name and parameters hash (not raw params—PII safe)
- Caller identity (agent DID, badge JTI, auth level)
- Decision and reason
- Timestamp and unique evidence ID
from capiscio_mcp import GuardConfig
config = GuardConfig(
min_trust_level=2, # Minimum trust level (0-4)
accept_level_zero=False, # Accept self-signed badges?
trusted_issuers=[ # List of trusted issuer DIDs
"did:web:registry.capisc.io",
],
allowed_tools=[ # Glob patterns for allowed tools
"read_*",
"list_*",
],
require_badge=True, # Deny anonymous/API key access
policy_version="v1.0", # Policy version for tracking
)from capiscio_mcp import VerifyConfig
config = VerifyConfig(
trusted_issuers=[...], # Trusted issuer DIDs
min_trust_level=2, # Minimum required level
accept_level_zero=False, # Accept self-signed servers?
offline_mode=False, # Skip revocation checks?
skip_origin_binding=False, # Skip host/path binding?
)| Variable | Description | Default |
|---|---|---|
CAPISCIO_CORE_ADDR |
External core address | (embedded mode) |
CAPISCIO_SERVER_ORIGIN |
Server origin for guard | (auto-detect) |
CAPISCIO_LOG_LEVEL |
Logging verbosity | info |
guard(config=None, min_trust_level=None, tool_name=None)— Async decoratorguard_sync(...)— Sync decoratorevaluate_tool_access(tool_name, params, credential, config)— Low-level APIcompute_params_hash(params)— Deterministic parameter hashingGuardConfig— Configuration dataclassGuardResult— Evaluation result dataclassGuardError— Exception for denied access
verify_server(server_did, server_badge, transport_origin, endpoint_path, config)— Async verificationverify_server_sync(...)— Sync verificationverify_server_strict(...)— Raises ServerVerifyError on any verification failureparse_http_headers(headers)— Extract identity from HTTP headersparse_jsonrpc_meta(meta)— Extract identity from MCP _metaVerifyConfig— Configuration dataclassVerifyResult— Verification result dataclassServerVerifyError— Exception for verification failures
generate_server_keypair(key_id, output_dir)— Generate Ed25519 keypairgenerate_server_keypair_sync(...)— Sync versionregister_server_identity(server_id, api_key, did, public_key, ca_url)— Register DID with registryregister_server_identity_sync(...)— Sync versionsetup_server_identity(server_id, api_key, ca_url, output_dir, key_id)— Combined setupsetup_server_identity_sync(...)— Sync versionRegistrationError— Exception for registration failuresKeyGenerationError— Exception for key generation failures
Decision— ALLOW / DENYAuthLevel— ANONYMOUS / API_KEY / BADGEDenyReason— Enumeration of denial reasonsTrustLevel— Trust levels 0-4 per RFC-002ServerState— VERIFIED_PRINCIPAL / DECLARED_PRINCIPAL / UNVERIFIED_ORIGINServerErrorCode— Enumeration of verification error codes
Requires pip install capiscio-mcp[mcp]:
CapiscioMCPServer(name, did, badge, ...)— FastMCP wrapper with trust enforcementCapiscioMCPServer.tool(min_trust_level=...)— Decorator for guarded toolsCapiscioMCPServer.run(transport="stdio")— Run the serverCapiscioMCPClient(command, args, ...)— Client for stdio transport*CapiscioMCPClient.call_tool(name, args)— Call a tool on the serverCapiscioMCPClient.list_tools()— List available tools
*Note: Server identity verification in CapiscioMCPClient requires MCP SDK support for _meta passthrough in initialize responses. This is not yet available, so min_trust_level and fail_on_unverified parameters are currently not enforced. Server-side trust enforcement via @server.tool(min_trust_level=...) works fully.
- RFC-006: MCP Tool Authority and Evidence
- RFC-007: MCP Server Identity Disclosure
- Server Registration Guide
- Server-Side Guide
- Client-Side Guide
- Evidence Logging Guide
# Clone repository
git clone https://github.com/capiscio/capiscio-mcp-python.git
cd capiscio-mcp-python
# Install development dependencies
pip install -e ".[dev]"
# Run tests
pytest -v
# Run tests with coverage
pytest --cov=capiscio_mcp --cov-report=html
# Type checking
mypy capiscio_mcp
# Linting
ruff check capiscio_mcpApache License 2.0
See CONTRIBUTING.md for guidelines.