#!/usr/bin/env python3 """ VaultMesh MCP Server Model Context Protocol server exposing VaultMesh Guardian, Treasury, Cognitive, and Auth tools. This enables Claude to operate as the 7th Organ of VaultMesh - the Cognitive Ψ-Layer. """ import asyncio import json import logging import os from datetime import datetime, timezone from pathlib import Path from typing import Any import blake3 # Try to import mcp, fallback gracefully if not available try: from mcp.server import Server from mcp.server.stdio import stdio_server from mcp.types import Tool, TextContent MCP_AVAILABLE = True except ImportError: MCP_AVAILABLE = False from .tools import ( # Guardian guardian_anchor_now, guardian_verify_receipt, guardian_status, # Treasury treasury_balance, treasury_debit, treasury_credit, treasury_create_budget, # Cognitive cognitive_context, cognitive_decide, cognitive_invoke_tem, cognitive_memory_get, cognitive_memory_set, cognitive_attest, cognitive_audit_trail, cognitive_oracle_chain, # Auth auth_challenge, auth_verify, auth_validate_token, auth_check_permission, check_profile_permission, get_profile_for_scope, auth_revoke, auth_list_sessions, auth_create_dev_session, auth_revoke, auth_list_sessions, ) # Setup logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger("vaultmesh-mcp") # VaultMesh root VAULTMESH_ROOT = Path(os.environ.get("VAULTMESH_ROOT", Path(__file__).parents[2])).resolve() MCP_RECEIPTS = VAULTMESH_ROOT / "receipts/mcp/mcp_calls.jsonl" # Tools that must remain callable without an authenticated session token. # These are the bootstrap endpoints required to obtain/check a session. OPEN_TOOLS = { "auth_challenge", "auth_verify", "auth_create_dev_session", "auth_check_permission", } def _vmhash_blake3(data: bytes) -> str: """VaultMesh hash: blake3:.""" return f"blake3:{blake3.blake3(data).hexdigest()}" def _redact_call_arguments(arguments: dict) -> dict: # Never persist session tokens in receipts. if not arguments: return {} redacted = dict(arguments) redacted.pop("session_token", None) return redacted def _emit_mcp_receipt(tool_name: str, arguments: dict, result: dict, caller: str = "did:vm:mcp:client") -> None: """Emit a receipt for every MCP tool call.""" MCP_RECEIPTS.parent.mkdir(parents=True, exist_ok=True) body = { "tool": tool_name, "arguments": _redact_call_arguments(arguments), "result_hash": _vmhash_blake3(json.dumps(result, sort_keys=True).encode()), "caller": caller, "success": "error" not in result, } receipt = { "schema_version": "2.0.0", "type": "mcp_tool_call", "timestamp": datetime.now(timezone.utc).isoformat(), "scroll": "mcp", "tags": ["mcp", "tool-call", tool_name], "root_hash": _vmhash_blake3(json.dumps(body, sort_keys=True).encode()), "body": body, } with open(MCP_RECEIPTS, "a") as f: f.write(json.dumps(receipt) + "\n") def require_session_and_permission(name: str, arguments: dict) -> tuple[bool, dict, str, dict | None]: """Fail-closed session + profile enforcement ahead of tool handlers. Returns (allowed, safe_args, caller, denial_result). - safe_args strips session_token so downstream handlers never see it. - caller is derived from the validated session (operator_did) when available. - denial_result is a structured error payload when denied. """ safe_args = dict(arguments or {}) caller = "did:vm:mcp:client" if name in OPEN_TOOLS: return True, safe_args, caller, None session_token = safe_args.pop("session_token", None) if not session_token: return False, safe_args, caller, { "error": "Missing session_token", "allowed": False, "reason": "Session required for non-auth tools", } validation = auth_validate_token(session_token) if not validation.get("valid"): return False, safe_args, caller, { "error": "Invalid session", "allowed": False, "reason": validation.get("error", "invalid_session"), } caller = validation.get("operator_did") or caller profile = get_profile_for_scope(str(validation.get("scope", "read"))) perm = check_profile_permission(profile, name) if not perm.get("allowed"): return False, safe_args, caller, { "error": "Permission denied", "allowed": False, "profile": perm.get("profile"), "reason": perm.get("reason", "denied"), } return True, safe_args, caller, None # ============================================================================= # TOOL DEFINITIONS # ============================================================================= TOOLS = [ # ------------------------------------------------------------------------- # GUARDIAN TOOLS # ------------------------------------------------------------------------- { "name": "guardian_anchor_now", "description": "Anchor all or specified scrolls to compute a Merkle root snapshot. Emits a guardian receipt.", "inputSchema": { "type": "object", "properties": { "scrolls": { "type": "array", "items": {"type": "string"}, "description": "List of scroll names to anchor. Omit for all scrolls.", }, "guardian_did": { "type": "string", "default": "did:vm:guardian:mcp", }, "backend": { "type": "string", "default": "local", "enum": ["local", "ethereum", "stellar"], }, }, }, }, { "name": "guardian_verify_receipt", "description": "Verify a receipt exists in a scroll's JSONL by its hash.", "inputSchema": { "type": "object", "properties": { "receipt_hash": {"type": "string"}, "scroll": {"type": "string", "default": "guardian"}, }, "required": ["receipt_hash"], }, }, { "name": "guardian_status", "description": "Get current status of all scrolls including Merkle roots and leaf counts.", "inputSchema": {"type": "object", "properties": {}}, }, # ------------------------------------------------------------------------- # TREASURY TOOLS # ------------------------------------------------------------------------- { "name": "treasury_create_budget", "description": "Create a new budget for tracking expenditures.", "inputSchema": { "type": "object", "properties": { "budget_id": {"type": "string"}, "name": {"type": "string"}, "allocated": {"type": "integer"}, "currency": {"type": "string", "default": "EUR"}, "created_by": {"type": "string", "default": "did:vm:mcp:treasury"}, }, "required": ["budget_id", "name", "allocated"], }, }, { "name": "treasury_balance", "description": "Get balance for a specific budget or all budgets.", "inputSchema": { "type": "object", "properties": { "budget_id": {"type": "string"}, }, }, }, { "name": "treasury_debit", "description": "Debit (spend) from a budget. Fails if insufficient funds.", "inputSchema": { "type": "object", "properties": { "budget_id": {"type": "string"}, "amount": {"type": "integer"}, "description": {"type": "string"}, "debited_by": {"type": "string", "default": "did:vm:mcp:treasury"}, }, "required": ["budget_id", "amount", "description"], }, }, { "name": "treasury_credit", "description": "Credit (add funds) to a budget.", "inputSchema": { "type": "object", "properties": { "budget_id": {"type": "string"}, "amount": {"type": "integer"}, "description": {"type": "string"}, "credited_by": {"type": "string", "default": "did:vm:mcp:treasury"}, }, "required": ["budget_id", "amount", "description"], }, }, # ------------------------------------------------------------------------- # COGNITIVE TOOLS (Claude as 7th Organ) # ------------------------------------------------------------------------- { "name": "cognitive_context", "description": "Read current VaultMesh context for AI reasoning. Aggregates alerts, health, receipts, threats, treasury, governance, and memory.", "inputSchema": { "type": "object", "properties": { "include": { "type": "array", "items": {"type": "string"}, "description": "Context types: alerts, health, receipts, threats, treasury, governance, memory", }, "session_id": {"type": "string"}, }, }, }, { "name": "cognitive_decide", "description": "Submit a reasoned decision with cryptographic attestation. Every decision is signed and anchored to ProofChain.", "inputSchema": { "type": "object", "properties": { "reasoning_chain": { "type": "array", "items": {"type": "string"}, "description": "List of reasoning steps leading to decision", }, "decision": { "type": "string", "description": "Decision type: invoke_tem, alert, remediate, approve, etc.", }, "confidence": { "type": "number", "minimum": 0, "maximum": 1, }, "evidence": { "type": "array", "items": {"type": "string"}, }, "operator_did": {"type": "string", "default": "did:vm:cognitive:claude"}, "auto_action_threshold": {"type": "number", "default": 0.95}, }, "required": ["reasoning_chain", "decision", "confidence"], }, }, { "name": "cognitive_invoke_tem", "description": "Invoke Tem (Guardian) with AI-detected threat pattern. Transmutes threats into defensive capabilities.", "inputSchema": { "type": "object", "properties": { "threat_type": { "type": "string", "description": "Category: replay_attack, intrusion, anomaly, credential_stuffing, etc.", }, "threat_id": {"type": "string"}, "target": {"type": "string"}, "evidence": { "type": "array", "items": {"type": "string"}, }, "recommended_transmutation": {"type": "string"}, "operator_did": {"type": "string", "default": "did:vm:cognitive:claude"}, }, "required": ["threat_type", "threat_id", "target", "evidence"], }, }, { "name": "cognitive_memory_get", "description": "Query conversation/reasoning memory from CRDT realm.", "inputSchema": { "type": "object", "properties": { "key": {"type": "string"}, "session_id": {"type": "string"}, "realm": {"type": "string", "default": "memory"}, }, "required": ["key"], }, }, { "name": "cognitive_memory_set", "description": "Store reasoning artifacts for future sessions. Uses CRDT-style merge.", "inputSchema": { "type": "object", "properties": { "key": {"type": "string"}, "value": {"type": "object"}, "session_id": {"type": "string"}, "realm": {"type": "string", "default": "memory"}, "merge": {"type": "boolean", "default": True}, }, "required": ["key", "value"], }, }, { "name": "cognitive_attest", "description": "Create cryptographic attestation of Claude's reasoning state. Anchors to external chains.", "inputSchema": { "type": "object", "properties": { "attestation_type": {"type": "string"}, "content": {"type": "object"}, "anchor_to": { "type": "array", "items": {"type": "string"}, "description": "Anchor backends: local, rfc3161, eth, btc", }, "operator_did": {"type": "string", "default": "did:vm:cognitive:claude"}, }, "required": ["attestation_type", "content"], }, }, { "name": "cognitive_audit_trail", "description": "Query historical AI decisions for audit with full provenance.", "inputSchema": { "type": "object", "properties": { "filter_type": {"type": "string"}, "time_range": { "type": "object", "properties": { "start": {"type": "string"}, "end": {"type": "string"}, }, }, "confidence_min": {"type": "number"}, "limit": {"type": "integer", "default": 100}, }, }, }, { "name": "cognitive_oracle_chain", "description": "Execute oracle chain with cognitive enhancement. Adds memory context and Tem awareness.", "inputSchema": { "type": "object", "properties": { "question": {"type": "string"}, "frameworks": { "type": "array", "items": {"type": "string"}, "description": "Compliance frameworks: GDPR, AI_ACT, NIS2, etc.", }, "max_docs": {"type": "integer", "default": 10}, "include_memory": {"type": "boolean", "default": True}, "session_id": {"type": "string"}, }, "required": ["question"], }, }, # ------------------------------------------------------------------------- # AUTH TOOLS # ------------------------------------------------------------------------- { "name": "auth_challenge", "description": "Generate an authentication challenge for an operator.", "inputSchema": { "type": "object", "properties": { "operator_pubkey_b64": {"type": "string"}, "scope": { "type": "string", "enum": ["read", "admin", "vault", "anchor", "cognitive"], "default": "read", }, "ttl_seconds": {"type": "integer", "default": 300}, }, "required": ["operator_pubkey_b64"], }, }, { "name": "auth_verify", "description": "Verify a signed challenge and issue session token.", "inputSchema": { "type": "object", "properties": { "challenge_id": {"type": "string"}, "signature_b64": {"type": "string"}, "ip_hint": {"type": "string"}, }, "required": ["challenge_id", "signature_b64"], }, }, { "name": "auth_check_permission", "description": "Check if a session has permission to call a tool.", "inputSchema": { "type": "object", "properties": { "token": {"type": "string"}, "tool_name": {"type": "string"}, }, "required": ["token", "tool_name"], }, }, { "name": "auth_create_dev_session", "description": "Create a development session for testing (DEV ONLY).", "inputSchema": { "type": "object", "properties": { "scope": {"type": "string", "default": "cognitive"}, "operator_did": {"type": "string", "default": "did:vm:cognitive:claude-dev"}, }, }, }, { "name": "auth_revoke", "description": "Revoke a session token.", "inputSchema": { "type": "object", "properties": { "token": {"type": "string"}, }, "required": ["token"], }, }, { "name": "auth_list_sessions", "description": "List all active sessions (admin only).", "inputSchema": {"type": "object", "properties": {}}, }, ] def handle_tool_call(name: str, arguments: dict) -> dict[str, Any]: """Dispatch tool call to appropriate handler.""" handlers = { # Guardian "guardian_anchor_now": guardian_anchor_now, "guardian_verify_receipt": guardian_verify_receipt, "guardian_status": guardian_status, # Treasury "treasury_create_budget": treasury_create_budget, "treasury_balance": treasury_balance, "treasury_debit": treasury_debit, "treasury_credit": treasury_credit, # Cognitive "cognitive_context": cognitive_context, "cognitive_decide": cognitive_decide, "cognitive_invoke_tem": cognitive_invoke_tem, "cognitive_memory_get": cognitive_memory_get, "cognitive_memory_set": cognitive_memory_set, "cognitive_attest": cognitive_attest, "cognitive_audit_trail": cognitive_audit_trail, "cognitive_oracle_chain": cognitive_oracle_chain, # Auth "auth_challenge": auth_challenge, "auth_verify": auth_verify, "auth_check_permission": auth_check_permission, "auth_create_dev_session": auth_create_dev_session, "auth_revoke": auth_revoke, "auth_list_sessions": auth_list_sessions, } if name not in handlers: return {"error": f"Unknown tool: {name}"} allowed, safe_args, caller, denial = require_session_and_permission(name, arguments) if not allowed: _emit_mcp_receipt(name, safe_args, denial, caller=caller) return denial result = handlers[name](**safe_args) # Emit receipt for the tool call _emit_mcp_receipt(name, safe_args, result, caller=caller) return result if MCP_AVAILABLE: # Create MCP server app = Server("vaultmesh-mcp") @app.list_tools() async def list_tools() -> list[Tool]: """List available VaultMesh tools.""" return [Tool(**t) for t in TOOLS] @app.call_tool() async def call_tool(name: str, arguments: dict) -> list[TextContent]: """Handle tool invocation.""" logger.info(f"Tool call: {name} with {arguments}") result = handle_tool_call(name, arguments) return [TextContent(type="text", text=json.dumps(result, indent=2))] async def main(): """Run the MCP server.""" if not MCP_AVAILABLE: print("MCP library not available. Install with: pip install mcp") return logger.info(f"Starting VaultMesh MCP Server (root: {VAULTMESH_ROOT})") logger.info(f"Tools registered: {len(TOOLS)}") async with stdio_server() as (read_stream, write_stream): await app.run(read_stream, write_stream, app.create_initialization_options()) def run_standalone(): """Run as standalone CLI for testing without MCP.""" import sys if len(sys.argv) < 2: print("VaultMesh MCP Server - Standalone Mode") print(f"\nVaultMesh Root: {VAULTMESH_ROOT}") print(f"\nRegistered Tools ({len(TOOLS)}):") print("-" * 60) for tool in TOOLS: print(f" {tool['name']}") print(f" {tool['description'][:70]}...") print("-" * 60) print("\nUsage: python -m vaultmesh_mcp.server [json_args]") print("\nExample:") print(' python -m vaultmesh_mcp.server cognitive_context \'{"include": ["health"]}\'') return tool_name = sys.argv[1] args_str = sys.argv[2] if len(sys.argv) > 2 else "{}" try: arguments = json.loads(args_str) except json.JSONDecodeError: print(f"Invalid JSON arguments: {args_str}") return result = handle_tool_call(tool_name, arguments) print(json.dumps(result, indent=2)) if __name__ == "__main__": import sys # If any CLI arguments provided (other than module name), run standalone if len(sys.argv) > 1 or not MCP_AVAILABLE: run_standalone() else: asyncio.run(main())