Mở Đầu: Câu Chuyện Thực Tế Từ Hệ Thống Chăm Sóc Khách Hàng AI Thương Mại Điện Tử

Tôi vẫn nhớ rõ đêm mùng 3 Tết năm nay — hệ thống chăm sóc khách hàng AI của một trong những sàn thương mại điện tử lớn nhất Việt Nam bất ngờ chậm như rùa bò. Hàng nghìn khách hàng đang truy vấn về đơn hàng, tình trạng giao hàng, và chính sách đổi trả — nhưng mọi thứ đều timeout. Trong 45 phút đầu tiên, đội kỹ thuật của tôi gần như mò mẫm trong bóng tối vì không có log chi tiết, không biết request bị chặn ở đâu, và latency tăng từ 80ms lên hơn 8 giây. Đó là khoảnh khắc tôi quyết định đầu tư toàn bộ thời gian để xây dựng một hệ thống MCP Tool debugging chuẩn chỉnh. Kết quả? 6 tháng sau, đội của tôi xử lý sự cố trung bình trong 3 phút thay vì 45 phút như trước.

Bài viết này sẽ chia sẻ toàn bộ kinh nghiệm thực chiến về cách debug MCP Tool một cách hiệu quả, từ những lỗi đơn giản nhất cho đến những vấn đề phức tạp chỉ xuất hiện dưới tải cao. Tôi sẽ hướng dẫn bạn từng bước với code mẫu có thể sao chép và chạy ngay, đồng thời tích hợp với HolySheep AI — nền tảng mà tôi sử dụng cho hầu hết các dự án vì chi phí chỉ bằng 15% so với các provider phương Tây.

MCP Tool Là Gì và Tại Sao Cần Debugging?

MCP (Model Context Protocol) Tool là cách để các mô hình AI tương tác với external tools và services. Khi bạn build một hệ thống RAG cho doanh nghiệp hoặc một ứng dụng AI agent phức tạp, MCP Tool đóng vai trò như cầu nối giữa LLM và thế giới thực — truy vấn database, gọi API bên thứ ba, thao tác file system, hoặc kết nối với các dịch vụ như Slack, Notion, GitHub.

Vấn đề là khi mọi thứ hoạt động tốt ở local nhưng fail khi deploy lên production, hoặc khi latency tăng đột ngột vào giờ cao điểm, bạn cần một phương pháp systematic để trace và debug. Đây là lúc MCP Tool debugging trở nên quan trọng.

Cài Đặt Môi Trường Debug MCP Tool

Trước khi đi vào các kỹ thuật debugging, chúng ta cần setup môi trường phù hợp. Tôi khuyên dùng một project riêng biệt để debug thay vì debug trực tiếp trong production code.

# Tạo virtual environment cho MCP debugging
python -m venv mcp-debug-env
source mcp-debug-env/bin/activate  # Linux/Mac

hoặc: mcp-debug-env\Scripts\activate # Windows

Cài đặt dependencies cần thiết

pip install anthropic openai httpx structlog python-dotenv pip install mcp # MCP SDK nếu bạn tự build tools

Cài đặt logging để trace requests

pip install loguru rich

Verify installation

python -c "import mcp; print(f'MCP version: {mcp.__version__}')"

Sau khi cài đặt xong, tạo file cấu hình môi trường với API key từ HolySheep AI — nơi tôi nhận được latency trung bình chỉ 45ms cho các request GPT-4o và chi phí chỉ $0.42/1M tokens cho DeepSeek V3.2.

# config.py - Cấu hình MCP Tool với HolySheep AI

import os
from dotenv import load_dotenv

load_dotenv()  # Load .env file

=== MCP Tool Configuration ===

MCP_CONFIG = { "base_url": "https://api.holysheep.ai/v1", # KHÔNG dùng api.openai.com "api_key": os.getenv("YOUR_HOLYSHEEP_API_KEY"), "model": "gpt-4o", # Hoặc deepseek-v3.2 cho chi phí thấp "timeout": 30, # Timeout 30 giây cho mỗi request "max_retries": 3, }

=== Logging Configuration ===

LOGGING_CONFIG = { "level": "DEBUG", # Bật DEBUG để xem chi tiết "format": "{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {name}:{function}:{line} - {message}", "rotation": "100 MB", "retention": "7 days", "output": "./logs/mcp_debug.log", }

=== Tool Registry ===

AVAILABLE_TOOLS = { "search_database": { "endpoint": "/tools/search", "timeout": 10, "retry_on_failure": True, }, "get_customer_info": { "endpoint": "/tools/customer", "timeout": 5, "retry_on_failure": True, }, "update_order_status": { "endpoint": "/tools/order", "timeout": 15, "retry_on_failure": False, # Không retry cho operations ghi dữ liệu } } print("✅ MCP Configuration loaded successfully") print(f" Base URL: {MCP_CONFIG['base_url']}") print(f" Model: {MCP_CONFIG['model']}")

Kỹ Thuật Log Tracing Cho MCP Tool

Khi debug MCP Tool, log là vũ khí quan trọng nhất của bạn. Tôi đã xây dựng một logging wrapper tùy chỉnh giúp track mọi request/response một cách chi tiết, bao gồm cả token usage và latency thực tế.

# mcp_debug_logger.py - Advanced logging cho MCP Tool

import time
import structlog
import httpx
from datetime import datetime
from typing import Any, Dict, Optional
from config import MCP_CONFIG, LOGGING_CONFIG

Configure structlog cho structured logging

structlog.configure( processors=[ structlog.stdlib.filter_by_level, structlog.stdlib.add_logger_name, structlog.stdlib.add_log_level, structlog.processors.TimeStamper(fmt="iso"), structlog.processors.StackInfoRenderer(), structlog.processors.format_exc_info, structlog.processors.JSONRenderer() ], wrapper_class=structlog.stdlib.BoundLogger, context_class=dict, logger_factory=structlog.stdlib.LoggerFactory(), cache_logger_on_first_use=True, ) logger = structlog.get_logger("mcp_debug") class MCPDebugClient: """Enhanced MCP client với built-in debugging và tracing""" def __init__(self, config: Dict[str, Any]): self.base_url = config["base_url"] self.api_key = config["api_key"] self.model = config["model"] self.timeout = config["timeout"] self.max_retries = config["max_retries"] # Metrics tracking self.metrics = { "total_requests": 0, "failed_requests": 0, "total_tokens": 0, "latencies": [], "errors_by_type": {} } # HTTP Client với connection pooling self.client = httpx.Client( base_url=self.base_url, headers={ "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json" }, timeout=self.timeout, limits=httpx.Limits(max_connections=100, max_keepalive_connections=20) ) logger.info("mcp_debug_client_initialized", base_url=self.base_url, model=self.model) def call_tool(self, tool_name: str, parameters: Dict[str, Any]) -> Dict[str, Any]: """ Gọi MCP tool với full tracing và error tracking """ start_time = time.perf_counter() request_id = f"req_{datetime.now().strftime('%Y%m%d_%H%M%S_%f')}" logger.info("mcp_tool_call_started", request_id=request_id, tool_name=tool_name, parameters=parameters) try: # Gọi API response = self._execute_with_retry(tool_name, parameters) # Calculate metrics latency_ms = (time.perf_counter() - start_time) * 1000 self._update_metrics("success", latency_ms, response) logger.info("mcp_tool_call_completed", request_id=request_id, tool_name=tool_name, latency_ms=round(latency_ms, 2), status="success") return { "success": True, "data": response, "latency_ms": latency_ms, "request_id": request_id } except httpx.TimeoutException as e: latency_ms = (time.perf_counter() - start_time) * 1000 self._update_metrics("timeout", latency_ms, None) logger.error("mcp_tool_timeout", request_id=request_id, tool_name=tool_name, latency_ms=round(latency_ms, 2), error=str(e)) return { "success": False, "error": "timeout", "message": f"Request timeout sau {self.timeout}s", "latency_ms": latency_ms, "request_id": request_id } except httpx.HTTPStatusError as e: latency_ms = (time.perf_counter() - start_time) * 1000 self._update_metrics("http_error", latency_ms, None) logger.error("mcp_tool_http_error", request_id=request_id, tool_name=tool_name, status_code=e.response.status_code, response_body=e.response.text[:500], latency_ms=round(latency_ms, 2)) return { "success": False, "error": "http_error", "status_code": e.response.status_code, "message": e.response.text, "latency_ms": latency_ms, "request_id": request_id } except Exception as e: latency_ms = (time.perf_counter() - start_time) * 1000 self._update_metrics("unknown_error", latency_ms, None) logger.exception("mcp_tool_unknown_error", request_id=request_id, tool_name=tool_name, error_type=type(e).__name__, error_message=str(e)) return { "success": False, "error": type(e).__name__, "message": str(e), "latency_ms": latency_ms, "request_id": request_id } def _execute_with_retry(self, tool_name: str, parameters: Dict) -> Dict: """Execute request với retry logic""" for attempt in range(self.max_retries): try: response = self.client.post( "/chat/completions", json={ "model": self.model, "messages": [ {"role": "user", "content": f"Execute tool: {tool_name} with params: {parameters}"} ], "tools": [{"type": "function", "function": {"name": tool_name}}] } ) response.raise_for_status() return response.json() except httpx.HTTPStatusError as e: if e.response.status_code >= 500 and attempt < self.max_retries - 1: logger.warning("retry_attempt", attempt=attempt + 1, max_retries=self.max_retries, status_code=e.response.status_code) time.sleep(2 ** attempt) # Exponential backoff continue raise raise httpx.HTTPStatusError("Max retries exceeded", request=None, response=None) def _update_metrics(self, status: str, latency_ms: float, response: Optional[Dict]): """Update internal metrics""" self.metrics["total_requests"] += 1 if status != "success": self.metrics["failed_requests"] += 1 self.metrics["latencies"].append(latency_ms) if status not in self.metrics["errors_by_type"]: self.metrics["errors_by_type"][status] = 0 self.metrics["errors_by_type"][status] += 1 # Track token usage nếu có response if response and "usage" in response: usage = response["usage"] self.metrics["total_tokens"] += usage.get("total_tokens", 0) def get_metrics_summary(self) -> Dict[str, Any]: """Trả về summary của các metrics""" latencies = self.metrics["latencies"] return { "total_requests": self.metrics["total_requests"], "failed_requests": self.metrics["failed_requests"], "success_rate": round( (self.metrics["total_requests"] - self.metrics["failed_requests"]) / self.metrics["total_requests"] * 100, 2 ) if self.metrics["total_requests"] > 0 else 0, "avg_latency_ms": round(sum(latencies) / len(latencies), 2) if latencies else 0, "p50_latency_ms": round(sorted(latencies)[len(latencies)//2], 2) if latencies else 0, "p95_latency_ms": round(sorted(latencies)[int(len(latencies)*0.95)], 2) if latencies else 0, "p99_latency_ms": round(sorted(latencies)[int(len(latencies)*0.99)], 2) if latencies else 0, "total_tokens": self.metrics["total_tokens"], "errors_by_type": self.metrics["errors_by_type"] } def close(self): self.client.close() logger.info("mcp_client_closed", metrics=self.get_metrics_summary())

=== Demo Usage ===

if __name__ == "__main__": # Initialize client client = MCPDebugClient(MCP_CONFIG) # Test với sample tool call result = client.call_tool( tool_name="search_database", parameters={"query": "customer orders", "limit": 10} ) print("\n" + "="*60) print("METRICS SUMMARY") print("="*60) for key, value in client.get_metrics_summary().items(): print(f" {key}: {value}") client.close()

Các Chiến Lược Debugging MCP Tool Hiệu Quả

1. Request/Response Interception

Một trong những kỹ thuật quan trọng nhất là intercept tất cả requests và responses để xem chính xác dữ liệu đang được truyền qua lại. Điều này giúp bạn phát hiện các vấn đề như malformed JSON, thiếu fields, hoặc data type mismatch.

# mcp_interceptor.py - Request/Response Interception

import json
import base64
from functools import wraps
from typing import Callable, Any
import httpx

class MCPInterceptor:
    """
    Interceptor để debug tất cả HTTP traffic
    Rất hữu ích để phát hiện vấn đề về data format hoặc authentication
    """
    
    def __init__(self):
        self.request_log = []
        self.response_log = []
        self.error_log = []
    
    def intercept_request(self, method: str, url: str, headers: dict, body: Any) -> dict:
        """Log request trước khi gửi"""
        request_entry = {
            "type": "request",
            "method": method,
            "url": url,
            "timestamp": datetime.now().isoformat(),
            "headers": self._sanitize_headers(headers),
            "body": body
        }
        
        self.request_log.append(request_entry)
        
        # Pretty print for debugging
        print(f"\n🔵 REQUEST [{method}] {url}")
        print(f"   Headers: {json.dumps(request_entry['headers'], indent=2)}")
        if body:
            print(f"   Body: {json.dumps(body, indent=2, ensure_ascii=False)}")
        
        return request_entry
    
    def intercept_response(self, response: httpx.Response, request_entry: dict) -> dict:
        """Log response sau khi nhận"""
        response_entry = {
            "type": "response",
            "status_code": response.status_code,
            "headers": dict(response.headers),
            "body": response.text[:5000] if response.text else None,  # Limit size
            "timestamp": datetime.now().isoformat(),
            "request_url": request_entry["url"]
        }
        
        self.response_log.append(response_entry)
        
        # Pretty print
        status_emoji = "✅" if response.status_code < 400 else "❌"
        print(f"\n{status_emoji} RESPONSE [{response.status_code}] {request_entry['url']}")
        print(f"   Time: {response_entry['timestamp']}")
        if response_entry['body']:
            try:
                parsed = json.loads(response_entry['body'])
                print(f"   Body: {json.dumps(parsed, indent=2, ensure_ascii=False)[:1000]}")
            except:
                print(f"   Body: {response_entry['body'][:500]}")
        
        return response_entry
    
    def intercept_error(self, error: Exception, request_entry: dict):
        """Log errors"""
        error_entry = {
            "type": "error",
            "error_type": type(error).__name__,
            "error_message": str(error),
            "timestamp": datetime.now().isoformat(),
            "request_url": request_entry["url"]
        }
        
        self.error_log.append(error_entry)
        print(f"\n💥 ERROR: {error_entry['error_type']}")
        print(f"   Message: {error_entry['error_message']}")
        print(f"   Request: {error_entry['request_url