Claude Skills Guide

MCP Server Logging, Audit Trail, and Security Guide

When building Model Context Protocol (MCP) servers, logging and audit trails are critical for debugging, compliance, and security monitoring. This guide covers practical approaches to implementing secure logging systems for your MCP servers, with code examples you can adapt immediately.

Why Logging Matters for MCP Servers

MCP servers act as bridges between Claude and external services Each request passing through your server represents a potential security boundary crossing. Without proper logging, you lose visibility into:

Whether you’re building a simple MCP server for personal use or deploying one across an organization, implementing structured logging from day one saves significant debugging time

Structured Logging Implementation

The foundation of any logging system is structured output. Instead of plain text messages, emit JSON objects that are machine-parseable:

import logging
import json
from datetime import datetime
from typing import Any

class StructuredLogger:
    def __init__(self, server_name: str):
        self.logger = logging.getLogger(server_name)
        self.logger.setLevel(logging.INFO)
        
        # Console handler with JSON formatter
        handler = logging.StreamHandler()
        handler.setFormatter(JsonFormatter())
        self.logger.addHandler(handler)
    
    def log_request(self, tool_name: str, params: dict, user_id: str = "anonymous"):
        self.logger.info({
            "event": "tool_request",
            "tool": tool_name,
            "params": self._redact_sensitive(params),
            "user": user_id,
            "timestamp": datetime.utcnow().isoformat()
        })
    
    def _redact_sensitive(self, data: dict) -> dict:
        """Remove sensitive fields before logging"""
        sensitive_keys = {"password", "api_key", "token", "secret"}
        return {k: "***REDACTED***" if k.lower() in sensitive_keys else v 
                for k, v in data.items()}

class JsonFormatter(logging.Formatter):
    def format(self, record):
        return json.dumps(record.getMessage())

This pattern ensures consistent log structure across your server. The _redact_sensitive method prevents accidental exposure of credentials—essential for security-sensitive deployments.

Building an Audit Trail

An audit trail goes beyond basic logging by recording the complete lifecycle of each operation. For MCP servers, this typically means capturing:

  1. Authentication events (login, logout, token refresh)
  2. Authorization decisions (permission granted or denied)
  3. Tool invocations with request/response pairs
  4. Data access events (read, write, delete)
  5. Configuration changes
import sqlite3
from pathlib import Path
from datetime import datetime

class AuditTrail:
    def __init__(self, db_path: str = "audit.db"):
        self.conn = sqlite3.connect(db_path)
        self._init_schema()
    
    def _init_schema(self):
        self.conn.execute("""
            CREATE TABLE IF NOT EXISTS audit_log (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                timestamp TEXT NOT NULL,
                event_type TEXT NOT NULL,
                user_id TEXT,
                resource TEXT,
                action TEXT,
                success INTEGER,
                details TEXT,
                ip_address TEXT
            )
        """)
        self.conn.execute("""
            CREATE INDEX idx_timestamp ON audit_log(timestamp)
        """)
        self.conn.execute("""
            CREATE INDEX idx_user ON audit_log(user_id)
        """)
    
    def record(self, event_type: str, user_id: str, 
               resource: str, action: str, success: bool, 
               details: dict = None, ip_address: str = None):
        self.conn.execute("""
            INSERT INTO audit_log 
            (timestamp, event_type, user_id, resource, action, success, details, ip_address)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?)
        """, (
            datetime.utcnow().isoformat(),
            event_type,
            user_id,
            resource,
            action,
            1 if success else 0,
            json.dumps(details) if details else None,
            ip_address
        ))
        self.conn.commit()
    
    def query(self, user_id: str = None, 
              start_date: str = None, end_date: str = None,
              limit: int = 100):
        query = "SELECT * FROM audit_log WHERE 1=1"
        params = []
        
        if user_id:
            query += " AND user_id = ?"
            params.append(user_id)
        if start_date:
            query += " AND timestamp >= ?"
            params.append(start_date)
        if end_date:
            query += " AND timestamp <= ?"
            params.append(end_date)
        
        query += " ORDER BY timestamp DESC LIMIT ?"
        params.append(limit)
        
        return self.conn.execute(query, params).fetchall()

This SQLite-backed audit trail provides queryable records for compliance reporting and incident investigation. The indexed columns ensure performance even with high-volume logging.

Security Best Practices

Beyond logging implementation, consider these security measures:

Rotate log files regularly. Implement log rotation to prevent disk exhaustion and ensure old audit data is archived per your retention policy.

Encrypt sensitive logs. If your MCP server handles HIPAA, PCI-DSS, or other regulated data, encrypt logs at rest using tools like cryptography in Python:

from cryptography.fernet import Fernet

class EncryptedLogger:
    def __init__(self, key: bytes):
        self.cipher = Fernet(key)
    
    def encrypt_and_log(self, message: str):
        encrypted = self.cipher.encrypt(message.encode())
        # Store encrypted payload

Implement log integrity. Use hash chains or digital signatures to detect tampering:

import hmac
import hashlib

class SignedLogger:
    def __init__(self, secret_key: str):
        self.secret = secret_key.encode()
        self.last_hash = None
    
    def sign_entry(self, log_entry: str) -> str:
        message = f"{self.last_hash or ''}{log_entry}".encode()
        signature = hmac.new(self.secret, message, hashlib.sha256).hexdigest()
        self.last_hash = signature
        return f"{log_entry}\nHMAC: {signature}"

Integrating with Claude Skills

When developing MCP servers, pair your logging implementation with Claude’s diagnostic skills. The superpower skill provides general debugging guidance, while specific skills like pdf for document processing or frontend-design for UI components can help you build better server interfaces.

For test-driven development of your logging systems, the tdd skill helps you write tests before implementing log handlers. Claude Code can audit your logging code for security issues before deployment.

Monitoring and Alerting

Logging without monitoring provides historical value but misses active threats. Implement basic alerting:

class AlertingLogger(StructuredLogger):
    def __init__(self, server_name: str, alert_threshold: int = 10):
        super().__init__(server_name)
        self.failed_requests = {}
        self.alert_threshold = alert_threshold
    
    def log_request(self, tool_name: str, params: dict, 
                    user_id: str = "anonymous", success: bool = True):
        super().log_request(tool_name, params, user_id)
        
        if not success:
            self.failed_requests[user_id] = self.failed_requests.get(user_id, 0) + 1
            
            if self.failed_requests[user_id] >= self.alert_threshold:
                self._send_alert(user_id)
    
    def _send_alert(self, user_id: str):
        # Integration with PagerDuty, Slack, email, etc.
        print(f"ALERT: User {user_id} exceeded failure threshold")

This pattern integrates with monitoring tools like Datadog or Prometheus for production deployments.

Conclusion

Implementing logging and audit trails for MCP servers requires upfront design decisions that pay dividends throughout your project’s lifecycle. Start with structured JSON logging, build queryable audit trails, and layer security measures appropriate to your data sensitivity. The patterns shown here provide a foundation you can extend based on specific compliance requirements and operational needs.

Remember: the best logging system is one that gets reviewed. Build dashboards, set up regular log reviews, and treat anomalies as investigation opportunities.


Built by theluckystrike — More at zovo.one