Skip to content

Security và Best Practices

Overview

Security là critical aspect của MCP servers vì chúng act as bridge giữa AI agents và external systems. Tutorial này covers security considerations, best practices, và production deployment guidelines.

Security Model

Threat Model

MCP servers face các threats sau:

  1. Unauthorized Access

    • AI agents accessing restricted resources
    • Privilege escalation
    • Data exfiltration
  2. Malicious Input

    • SQL injection
    • Path traversal
    • Command injection
  3. Resource Abuse

    • Denial of service
    • Resource exhaustion
    • Rate limiting bypass
  4. Data Exposure

    • Sensitive data leakage
    • Log exposure
    • Memory dumps

Security Layers

┌─────────────────────────────────────┐
│           AI Agent                 │
└─────────────┬───────────────────────┘
              │ Authentication
┌─────────────▼───────────────────────┐
│        MCP Server                   │
│  ┌─────────────┐  ┌─────────────┐   │
│  │   AuthZ     │  │   Validation │   │
│  └─────────────┘  └─────────────┘   │
└─────────────┬───────────────────────┘
              │ Authorization
┌─────────────▼───────────────────────┐
│      External Systems               │
│  ┌─────────────┐  ┌─────────────┐   │
│  │ Database    │  │ File System │   │
│  └─────────────┘  └─────────────┘   │
└─────────────────────────────────────┘

Authentication

1. JWT-based Authentication

typescript
// JWT Authentication middleware
import jwt from 'jsonwebtoken';

interface JWTPayload {
  userId: string;
  permissions: string[];
  expiresAt: number;
}

class JWTAuthenticator {
  private secretKey: string;
  private algorithm: jwt.Algorithm = 'HS256';

  constructor(secretKey: string) {
    this.secretKey = secretKey;
  }

  generateToken(payload: Omit<JWTPayload, 'expiresAt'>): string {
    const tokenPayload: JWTPayload = {
      ...payload,
      expiresAt: Date.now() + (24 * 60 * 60 * 1000) // 24 hours
    };

    return jwt.sign(tokenPayload, this.secretKey, {
      algorithm: this.algorithm,
      expiresIn: '24h'
    });
  }

  verifyToken(token: string): JWTPayload {
    try {
      const decoded = jwt.verify(token, this.secretKey, {
        algorithms: [this.algorithm]
      }) as JWTPayload;

      // Check expiration
      if (Date.now() > decoded.expiresAt) {
        throw new Error('Token expired');
      }

      return decoded;
    } catch (error) {
      throw new Error('Invalid authentication token');
    }
  }

  middleware() {
    return (req: any, res: any, next: any) => {
      const token = req.headers.authorization?.replace('Bearer ', '');
      
      if (!token) {
        return res.status(401).json({ error: 'Authentication required' });
      }

      try {
        const payload = this.verifyToken(token);
        req.user = payload;
        next();
      } catch (error) {
        return res.status(401).json({ error: 'Invalid token' });
      }
    };
  }
}

2. API Key Authentication

typescript
// API Key authentication
import crypto from 'crypto';

interface APIKey {
  id: string;
  keyHash: string;
  permissions: string[];
  createdAt: Date;
  lastUsed?: Date;
}

class APIKeyAuthenticator {
  private keys: Map<string, APIKey> = new Map();

  generateAPIKey(permissions: string[]): { id: string; key: string } {
    const id = crypto.randomUUID();
    const key = crypto.randomBytes(32).toString('hex');
    const keyHash = crypto.createHash('sha256').update(key).digest('hex');

    this.keys.set(id, {
      id,
      keyHash,
      permissions,
      createdAt: new Date()
    });

    return { id, key };
  }

  verifyAPIKey(key: string): APIKey {
    const keyHash = crypto.createHash('sha256').update(key).digest('hex');
    
    for (const apiKey of this.keys.values()) {
      if (apiKey.keyHash === keyHash) {
        apiKey.lastUsed = new Date();
        return apiKey;
      }
    }

    throw new Error('Invalid API key');
  }

  revokeAPIKey(id: string) {
    this.keys.delete(id);
  }
}

3. OAuth 2.0 Integration

typescript
// OAuth 2.0 authentication
import { AuthorizationCode } from 'simple-oauth2';

class OAuth2Authenticator {
  private oauth2client: AuthorizationCode;

  constructor(config: {
    clientId: string;
    clientSecret: string;
    authHost: string;
    tokenHost: string;
  }) {
    this.oauth2client = new AuthorizationCode({
      client: {
        id: config.clientId,
        secret: config.clientSecret,
      },
      auth: {
        tokenHost: config.tokenHost,
        authorizeHost: config.authHost,
      },
    });
  }

  getAuthorizationURL(scopes: string[]): string {
    return this.oauth2client.authorizeURL({
      scope: scopes.join(' '),
      state: crypto.randomBytes(16).toString('hex')
    });
  }

  async exchangeCodeForToken(code: string) {
    const result = await this.oauth2client.getToken({
      code,
      redirect_uri: 'http://localhost:3000/callback'
    });

    return result.token;
  }
}

Authorization

1. Role-Based Access Control (RBAC)

typescript
// RBAC implementation
interface Role {
  name: string;
  permissions: string[];
}

interface User {
  id: string;
  roles: string[];
  customPermissions?: string[];
}

class RBACAuthorizer {
  private roles: Map<string, Role> = new Map();
  private users: Map<string, User> = new Map();

  constructor() {
    this.initializeDefaultRoles();
  }

  private initializeDefaultRoles() {
    // Define default roles
    this.roles.set('admin', {
      name: 'admin',
      permissions: ['*'] // All permissions
    });

    this.roles.set('developer', {
      name: 'developer',
      permissions: [
        'file:read',
        'file:write',
        'database:read',
        'database:write',
        'api:call'
      ]
    });

    this.roles.set('analyst', {
      name: 'analyst',
      permissions: [
        'file:read',
        'database:read',
        'api:call'
      ]
    });

    this.roles.set('readonly', {
      name: 'readonly',
      permissions: [
        'file:read',
        'database:read'
      ]
    });
  }

  hasPermission(userId: string, resource: string, action: string): boolean {
    const user = this.users.get(userId);
    if (!user) {
      return false;
    }

    // Check custom permissions first
    if (user.customPermissions) {
      const customPerm = `${resource}:${action}`;
      const wildcardPerm = `${resource}:*`;
      const globalPerm = '*';

      if (user.customPermissions.includes(customPerm) ||
          user.customPermissions.includes(wildcardPerm) ||
          user.customPermissions.includes(globalPerm)) {
        return true;
      }
    }

    // Check role permissions
    for (const roleName of user.roles) {
      const role = this.roles.get(roleName);
      if (!role) continue;

      const rolePerm = `${resource}:${action}`;
      const roleWildcard = `${resource}:*`;
      const roleGlobal = '*';

      if (role.permissions.includes(rolePerm) ||
          role.permissions.includes(roleWildcard) ||
          role.permissions.includes(roleGlobal)) {
        return true;
      }
    }

    return false;
  }

  assignRole(userId: string, roleName: string) {
    const user = this.users.get(userId) || { id: userId, roles: [] };
    if (!user.roles.includes(roleName)) {
      user.roles.push(roleName);
      this.users.set(userId, user);
    }
  }

  grantCustomPermission(userId: string, permission: string) {
    const user = this.users.get(userId) || { id: userId, roles: [], customPermissions: [] };
    user.customPermissions = user.customPermissions || [];
    if (!user.customPermissions.includes(permission)) {
      user.customPermissions.push(permission);
      this.users.set(userId, user);
    }
  }
}

2. Attribute-Based Access Control (ABAC)

typescript
// ABAC implementation
interface Policy {
  id: string;
  name: string;
  conditions: PolicyCondition[];
  effect: 'allow' | 'deny';
  priority: number;
}

interface PolicyCondition {
  attribute: string;
  operator: 'eq' | 'ne' | 'in' | 'contains' | 'startsWith' | 'endsWith';
  value: any;
}

interface AccessRequest {
  userId: string;
  resource: string;
  action: string;
  context: {
    time: Date;
    ip: string;
    userAgent?: string;
    [key: string]: any;
  };
}

class ABACAuthorizer {
  private policies: Map<string, Policy> = new Map();

  addPolicy(policy: Policy) {
    this.policies.set(policy.id, policy);
  }

  evaluate(request: AccessRequest): boolean {
    // Sort policies by priority (higher priority first)
    const sortedPolicies = Array.from(this.policies.values())
      .sort((a, b) => b.priority - a.priority);

    for (const policy of sortedPolicies) {
      if (this.evaluateConditions(policy.conditions, request)) {
        return policy.effect === 'allow';
      }
    }

    // Default deny
    return false;
  }

  private evaluateConditions(conditions: PolicyCondition[], request: AccessRequest): boolean {
    return conditions.every(condition => {
      const attributeValue = this.getAttributeValue(condition.attribute, request);
      
      switch (condition.operator) {
        case 'eq':
          return attributeValue === condition.value;
        case 'ne':
          return attributeValue !== condition.value;
        case 'in':
          return Array.isArray(condition.value) && condition.value.includes(attributeValue);
        case 'contains':
          return typeof attributeValue === 'string' && attributeValue.includes(condition.value);
        case 'startsWith':
          return typeof attributeValue === 'string' && attributeValue.startsWith(condition.value);
        case 'endsWith':
          return typeof attributeValue === 'string' && attributeValue.endsWith(condition.value);
        default:
          return false;
      }
    });
  }

  private getAttributeValue(attribute: string, request: AccessRequest): any {
    const parts = attribute.split('.');
    let value: any = request;

    for (const part of parts) {
      value = value?.[part];
    }

    return value;
  }
}

Input Validation

1. Path Validation

typescript
// Path traversal protection
import * as path from 'path';

class PathValidator {
  private allowedPaths: string[];

  constructor(allowedPaths: string[]) {
    this.allowedPaths = allowedPaths.map(p => path.resolve(p));
  }

  validatePath(inputPath: string, basePath?: string): string {
    // Resolve the input path
    const resolvedPath = path.resolve(basePath || process.cwd(), inputPath);

    // Check if path is within allowed paths
    const isAllowed = this.allowedPaths.some(allowed => 
      resolvedPath.startsWith(allowed)
    );

    if (!isAllowed) {
      throw new Error(`Access denied: path outside allowed directories`);
    }

    // Check for path traversal attempts
    if (inputPath.includes('..') || inputPath.includes('~')) {
      throw new Error(`Invalid path: traversal characters not allowed`);
    }

    // Normalize path
    return path.normalize(resolvedPath);
  }

  validateFileName(fileName: string): void {
    // Check for invalid characters
    const invalidChars = /[<>:"|?*\x00-\x1f]/;
    if (invalidChars.test(fileName)) {
      throw new Error(`Invalid file name: contains invalid characters`);
    }

    // Check for reserved names (Windows)
    const reservedNames = [
      'CON', 'PRN', 'AUX', 'NUL',
      'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9',
      'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9'
    ];

    const nameWithoutExt = path.parse(fileName).name.toUpperCase();
    if (reservedNames.includes(nameWithoutExt)) {
      throw new Error(`Invalid file name: reserved name`);
    }

    // Check length
    if (fileName.length > 255) {
      throw new Error(`Invalid file name: too long`);
    }
  }
}

2. SQL Injection Prevention

typescript
// SQL injection protection
interface QueryOptions {
  allowedTables: string[];
  allowedOperations: string[];
  maxQueryLength: number;
}

class SQLValidator {
  private options: QueryOptions;

  constructor(options: QueryOptions) {
    this.options = options;
  }

  validateQuery(query: string): void {
    // Check query length
    if (query.length > this.options.maxQueryLength) {
      throw new Error(`Query too long: maximum ${this.options.maxQueryLength} characters`);
    }

    // Check for dangerous patterns
    const dangerousPatterns = [
      /DROP\s+TABLE/i,
      /DELETE\s+FROM/i,
      /UPDATE\s+.*\s+SET/i,
      /INSERT\s+INTO/i,
      /EXEC\s*\(/i,
      /EXECUTE\s*\(/i,
      /UNION\s+SELECT/i,
      /--/,
      /\/\*/,
      /\*\//
    ];

    for (const pattern of dangerousPatterns) {
      if (pattern.test(query)) {
        throw new Error(`Dangerous SQL pattern detected`);
      }
    }

    // Parse and validate tables
    this.validateTables(query);
  }

  private validateTables(query: string): void {
    const tableRegex = /FROM\s+(\w+)|JOIN\s+(\w+)/gi;
    let match;

    while ((match = tableRegex.exec(query)) !== null) {
      const tableName = match[1] || match[2];
      
      if (!this.options.allowedTables.includes(tableName)) {
        throw new Error(`Access denied: table '${tableName}' not allowed`);
      }
    }
  }

  sanitizeQuery(query: string, params: any[]): { query: string; params: any[] } {
    // Convert to parameterized query
    let paramIndex = 0;
    const sanitizedQuery = query.replace(/'([^']*)'/g, (match) => {
      return `$${++paramIndex}`;
    });

    return {
      query: sanitizedQuery,
      params: params
    };
  }
}

3. Command Injection Prevention

typescript
// Command injection protection
class CommandValidator {
  private allowedCommands: string[];

  constructor(allowedCommands: string[]) {
    this.allowedCommands = allowedCommands;
  }

  validateCommand(command: string, args: string[]): void {
    // Check if command is allowed
    if (!this.allowedCommands.includes(command)) {
      throw new Error(`Command not allowed: ${command}`);
    }

    // Validate arguments
    for (const arg of args) {
      this.validateArgument(arg);
    }
  }

  private validateArgument(arg: string): void {
    // Check for dangerous characters
    const dangerousChars = /[;&|`$(){}[\]<>*?~]/;
    if (dangerousChars.test(arg)) {
      throw new Error(`Invalid argument: contains dangerous characters`);
    }

    // Check for file paths
    if (arg.includes('/') || arg.includes('\\')) {
      throw new Error(`Invalid argument: file paths not allowed`);
    }

    // Check length
    if (arg.length > 1000) {
      throw new Error(`Invalid argument: too long`);
    }
  }

  sanitizeArgs(args: string[]): string[] {
    return args.map(arg => 
      arg.replace(/[;&|`$(){}[\]<>*?~]/g, '')
         .replace(/\s+/g, ' ')
         .trim()
    ).filter(arg => arg.length > 0);
  }
}

Rate Limiting

1. Token Bucket Algorithm

typescript
// Token bucket rate limiter
class TokenBucketRateLimiter {
  private buckets: Map<string, TokenBucket> = new Map();
  private refillRate: number; // tokens per second
  private bucketSize: number; // maximum tokens

  constructor(refillRate: number, bucketSize: number) {
    this.refillRate = refillRate;
    this.bucketSize = bucketSize;
  }

  isAllowed(clientId: string, tokens: number = 1): boolean {
    const bucket = this.getOrCreateBucket(clientId);
    this.refill(bucket);

    if (bucket.tokens >= tokens) {
      bucket.tokens -= tokens;
      return true;
    }

    return false;
  }

  private getOrCreateBucket(clientId: string): TokenBucket {
    let bucket = this.buckets.get(clientId);
    
    if (!bucket) {
      bucket = {
        tokens: this.bucketSize,
        lastRefill: Date.now()
      };
      this.buckets.set(clientId, bucket);
    }

    return bucket;
  }

  private refill(bucket: TokenBucket): void {
    const now = Date.now();
    const timePassed = (now - bucket.lastRefill) / 1000;
    const tokensToAdd = Math.floor(timePassed * this.refillRate);

    if (tokensToAdd > 0) {
      bucket.tokens = Math.min(bucket.tokens + tokensToAdd, this.bucketSize);
      bucket.lastRefill = now;
    }
  }

  cleanup(): void {
    const now = Date.now();
    const expireTime = 5 * 60 * 1000; // 5 minutes

    for (const [clientId, bucket] of this.buckets.entries()) {
      if (now - bucket.lastRefill > expireTime) {
        this.buckets.delete(clientId);
      }
    }
  }
}

interface TokenBucket {
  tokens: number;
  lastRefill: number;
}

2. Sliding Window Rate Limiter

typescript
// Sliding window rate limiter
class SlidingWindowRateLimiter {
  private windows: Map<string, number[]> = new Map();
  private windowSize: number; // in milliseconds
  private maxRequests: number;

  constructor(windowSize: number, maxRequests: number) {
    this.windowSize = windowSize;
    this.maxRequests = maxRequests;
  }

  isAllowed(clientId: string): boolean {
    const now = Date.now();
    const windowStart = now - this.windowSize;

    let requests = this.windows.get(clientId) || [];
    
    // Remove old requests
    requests = requests.filter(timestamp => timestamp > windowStart);

    // Check if under limit
    if (requests.length < this.maxRequests) {
      requests.push(now);
      this.windows.set(clientId, requests);
      return true;
    }

    return false;
  }

  getRemainingRequests(clientId: string): number {
    const now = Date.now();
    const windowStart = now - this.windowSize;

    const requests = this.windows.get(clientId) || [];
    const validRequests = requests.filter(timestamp => timestamp > windowStart);

    return Math.max(0, this.maxRequests - validRequests.length);
  }

  cleanup(): void {
    const now = Date.now();
    const expireTime = this.windowSize * 2;

    for (const [clientId, requests] of this.windows.entries()) {
      const windowStart = now - expireTime;
      const validRequests = requests.filter(timestamp => timestamp > windowStart);
      
      if (validRequests.length === 0) {
        this.windows.delete(clientId);
      } else {
        this.windows.set(clientId, validRequests);
      }
    }
  }
}

Audit Logging

1. Comprehensive Audit Logger

typescript
// Audit logging system
interface AuditEvent {
  id: string;
  timestamp: Date;
  userId: string;
  action: string;
  resource: string;
  result: 'success' | 'failure';
  details: any;
  ip: string;
  userAgent?: string;
  sessionId?: string;
}

class AuditLogger {
  private logFile: string;
  private encryptionKey: string;

  constructor(logFile: string, encryptionKey: string) {
    this.logFile = logFile;
    this.encryptionKey = encryptionKey;
  }

  async log(event: Omit<AuditEvent, 'id' | 'timestamp'>): Promise<void> {
    const auditEvent: AuditEvent = {
      id: crypto.randomUUID(),
      timestamp: new Date(),
      ...event
    };

    // Encrypt sensitive data
    const encryptedEvent = await this.encryptEvent(auditEvent);

    // Write to log file
    await this.writeToLog(encryptedEvent);

    // Also send to external monitoring
    await this.sendToMonitoring(auditEvent);
  }

  private async encryptEvent(event: AuditEvent): Promise<string> {
    const sensitiveFields = ['details', 'userAgent'];
    const sanitized = { ...event };

    for (const field of sensitiveFields) {
      if (sanitized[field]) {
        sanitized[field] = await this.encrypt(JSON.stringify(sanitized[field]));
      }
    }

    return JSON.stringify(sanitized);
  }

  private async encrypt(data: string): Promise<string> {
    const algorithm = 'aes-256-gcm';
    const iv = crypto.randomBytes(16);
    
    const cipher = crypto.createCipher(algorithm, this.encryptionKey);
    cipher.setAAD(Buffer.from('audit-log'));
    
    let encrypted = cipher.update(data, 'utf8', 'hex');
    encrypted += cipher.final('hex');
    
    const authTag = cipher.getAuthTag();
    
    return iv.toString('hex') + ':' + authTag.toString('hex') + ':' + encrypted;
  }

  private async writeToLog(event: string): Promise<void> {
    const logEntry = `${event}\n`;
    await fs.appendFile(this.logFile, logEntry);
  }

  private async sendToMonitoring(event: AuditEvent): Promise<void> {
    // Send to monitoring service
    // This could be Splunk, ELK, etc.
  }

  async searchEvents(query: {
    userId?: string;
    action?: string;
    resource?: string;
    startDate?: Date;
    endDate?: Date;
  }): Promise<AuditEvent[]> {
    // Search through audit logs
    // This is a simplified implementation
    const events: AuditEvent[] = [];
    
    // In production, use proper indexing and search
    return events;
  }
}

Production Deployment

1. Docker Security

dockerfile
# Secure Dockerfile
FROM node:20-alpine AS builder

# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001

# Set working directory
WORKDIR /app

# Copy package files
COPY package*.json ./

# Install dependencies
RUN npm ci --only=production && npm cache clean --force

# Copy source code
COPY --chown=nextjs:nodejs . .

# Build application
RUN npm run build

# Production stage
FROM node:20-alpine AS runner

# Install security updates
RUN apk update && apk upgrade && apk add --no-cache dumb-init

# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001

# Set working directory
WORKDIR /app

# Copy built application
COPY --from=builder --chown=nextjs:nodejs /app/dist ./dist
COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nextjs:nodejs /app/package.json ./package.json

# Set permissions
RUN chmod -R 755 /app/dist

# Switch to non-root user
USER nextjs

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD node dist/health-check.js

# Use dumb-init as PID 1
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "dist/index.js"]

2. Kubernetes Security

yaml
# Kubernetes deployment with security
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mcp-server
spec:
  replicas: 3
  selector:
    matchLabels:
      app: mcp-server
  template:
    metadata:
      labels:
        app: mcp-server
    spec:
      securityContext:
        runAsNonRoot: true
        runAsUser: 1001
        runAsGroup: 1001
        fsGroup: 1001
      containers:
      - name: mcp-server
        image: mcp-server:latest
        securityContext:
          allowPrivilegeEscalation: false
          readOnlyRootFilesystem: true
          capabilities:
            drop:
            - ALL
        env:
        - name: JWT_SECRET
          valueFrom:
            secretKeyRef:
              name: mcp-secrets
              key: jwt-secret
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: mcp-secrets
              key: database-url
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "200m"
        livenessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 5

3. Network Security

typescript
// Network security middleware
import rateLimit from 'express-rate-limit';
import helmet from 'helmet';
import cors from 'cors';

class SecurityMiddleware {
  static apply(app: Express) {
    // Security headers
    app.use(helmet({
      contentSecurityPolicy: {
        directives: {
          defaultSrc: ["'self'"],
          scriptSrc: ["'self'"],
          styleSrc: ["'self'", "'unsafe-inline'"],
          imgSrc: ["'self'", "data:", "https:"],
        },
      },
      hsts: {
        maxAge: 31536000,
        includeSubDomains: true,
        preload: true
      }
    }));

    // CORS
    app.use(cors({
      origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'],
      credentials: true,
      methods: ['GET', 'POST', 'PUT', 'DELETE'],
      allowedHeaders: ['Content-Type', 'Authorization']
    }));

    // Rate limiting
    const limiter = rateLimit({
      windowMs: 15 * 60 * 1000, // 15 minutes
      max: 100, // limit each IP to 100 requests per windowMs
      message: 'Too many requests from this IP',
      standardHeaders: true,
      legacyHeaders: false,
    });
    app.use(limiter);

    // Request size limit
    app.use(express.json({ limit: '10mb' }));
    app.use(express.urlencoded({ extended: true, limit: '10mb' }));

    // IP filtering
    app.use((req, res, next) => {
      const allowedIPs = process.env.ALLOWED_IPS?.split(',') || [];
      if (allowedIPs.length > 0 && !allowedIPs.includes(req.ip)) {
        return res.status(403).json({ error: 'IP not allowed' });
      }
      next();
    });
  }
}

Monitoring và Alerting

1. Metrics Collection

typescript
// Metrics collection
import { register, Counter, Histogram, Gauge } from 'prom-client';

class MetricsCollector {
  private requestCounter: Counter;
  private requestDuration: Histogram;
  private activeConnections: Gauge;
  private errorCounter: Counter;

  constructor() {
    this.requestCounter = new Counter({
      name: 'mcp_requests_total',
      help: 'Total number of MCP requests',
      labelNames: ['method', 'status']
    });

    this.requestDuration = new Histogram({
      name: 'mcp_request_duration_seconds',
      help: 'Duration of MCP requests',
      labelNames: ['method'],
      buckets: [0.1, 0.5, 1, 2, 5, 10]
    });

    this.activeConnections = new Gauge({
      name: 'mcp_active_connections',
      help: 'Number of active MCP connections'
    });

    this.errorCounter = new Counter({
      name: 'mcp_errors_total',
      help: 'Total number of MCP errors',
      labelNames: ['type', 'method']
    });

    register.registerMetric(this.requestCounter);
    register.registerMetric(this.requestDuration);
    register.registerMetric(this.activeConnections);
    register.registerMetric(this.errorCounter);
  }

  recordRequest(method: string, status: number, duration: number) {
    this.requestCounter.labels(method, status.toString()).inc();
    this.requestDuration.labels(method).observe(duration / 1000);
  }

  recordError(type: string, method: string) {
    this.errorCounter.labels(type, method).inc();
  }

  incrementConnections() {
    this.activeConnections.inc();
  }

  decrementConnections() {
    this.activeConnections.dec();
  }

  getMetrics() {
    return register.metrics();
  }
}

2. Health Checks

typescript
// Health check system
interface HealthCheck {
  name: string;
  check(): Promise<boolean>;
  timeout?: number;
}

class HealthChecker {
  private checks: Map<string, HealthCheck> = new Map();

  addCheck(check: HealthCheck) {
    this.checks.set(check.name, check);
  }

  async runChecks(): Promise<{
    status: 'healthy' | 'unhealthy';
    checks: { [name: string]: boolean };
    timestamp: Date;
  }> {
    const results: { [name: string]: boolean } = {};
    let allHealthy = true;

    for (const [name, check] of this.checks) {
      try {
        const timeout = check.timeout || 5000;
        const result = await Promise.race([
          check.check(),
          new Promise<boolean>((_, reject) => 
            setTimeout(() => reject(new Error('Timeout')), timeout)
          )
        ]);
        results[name] = result;
        if (!result) allHealthy = false;
      } catch (error) {
        results[name] = false;
        allHealthy = false;
      }
    }

    return {
      status: allHealthy ? 'healthy' : 'unhealthy',
      checks: results,
      timestamp: new Date()
    };
  }
}

// Example health checks
const healthChecker = new HealthChecker();

healthChecker.addCheck({
  name: 'database',
  async check() {
    // Check database connectivity
    return true;
  }
});

healthChecker.addCheck({
  name: 'memory',
  async check() {
    const usage = process.memoryUsage();
    return usage.heapUsed < 500 * 1024 * 1024; // 500MB limit
  }
});

Summary

Security Best Practices:

  • ✅ Authentication (JWT, API Keys, OAuth)
  • ✅ Authorization (RBAC, ABAC)
  • ✅ Input Validation (Path, SQL, Command)
  • ✅ Rate Limiting (Token Bucket, Sliding Window)
  • ✅ Audit Logging (Encrypted, Searchable)
  • ✅ Production Security (Docker, K8s, Network)
  • ✅ Monitoring (Metrics, Health Checks)

Key Principles:

  • Defense in Depth: Multiple security layers
  • Least Privilege: Minimal required permissions
  • Zero Trust: Verify everything
  • Fail Secure: Default deny
  • Audit Everything: Log all actions

Production Considerations:

  • Container security
  • Network segmentation
  • Secrets management
  • Monitoring and alerting
  • Regular security updates
  • Penetration testing

Tiếp theo: 8. MCP Ecosystem → Khám phá community servers

Internal documentation for iNET Portal