Model Context Protocol (MCP)

ساخت کلاینت‌های MCP مقاوم: تسلط بر مدارشکن‌ها و منطق تلاش مجدد

پروتکل زمینه مدل (MCP) یک تغییر پارادایم در نحوه تعامل عامل‌های هوش مصنوعی با ابزارها و منابع داده خارجی را نشان می‌دهد. با استقرار این عامل‌ها در سیستم‌های توزیع‌شده، قابلیت اطمینان اتصال به سرورهای MCP از راه دور حیاتی می‌شود. یک اختلال شبکه گذرا یا بارگذاری موقت سرور می‌تواند، اگر به درستی مدیریت نشود، منجر به شکست کامل عامل شود. برای ساخت برنامه‌های MCP در سطح تولید، باید فراتر از بلوک‌های ساده «try-catch» حرکت کنیم و الگوهای پیشرفته مقاومت مانند مدارشکن‌ها (Circuit Breakers) و منطق هوشمند تلاش مجدد را پیاده‌سازی کنیم.

چرا مقاومت در MCP اهمیت دارد؟

در یک معماری استاندارد MCP، کلاینت (که اغلب عامل LLM است) از طریق JSON-RPC روی جریان‌ها با سرور (ارائه‌دهنده ابزار) ارتباط برقرار می‌کند. برخلاف فراخوانی‌های تابعی محلی، سرورهای MCP از راه دور می‌توانند دچار تأخیر شبکه، انقضای مهلت زمانی (Timeout) و تخلیه منابع شوند. بدون مکانیزم‌های محافظتی، یک سرور معیوب می‌تواند کل بودجه مهلت زمانی کلاینت را مصرف کند و منجر به تأخیرهای قابل مشاهده برای کاربر یا قفل شدن (Deadlock) شود. الگوهای مقاومت تضمین می‌کنند که کلاینت پاسخگو باقی بماند و در صورت در دسترس نبودن یک ابزار خاص، به‌صورت ظریف عملکرد را کاهش دهد.

پیاده‌سازی منطق تلاش مجدد با پس‌نشینی نمایی

خط اول دفاع، منطق تلاش مجدد است. با این حال، تلاش‌های مجدد فوری و ساده می‌توانند سروری را که از قبل درگیر است، تحت فشار شدید قرار دهند. به جای آن، باید از پس‌نشینی نمایی (Exponential Backoff) همراه با لرزش (Jitter) استفاده کنیم. این استراتژی زمان انتظار بین تلاش‌ها را به‌صورت نمایی افزایش می‌دهد، بار را روی سرور کاهش می‌دهد و به مشکلات گذرا فرصت حل شدن می‌دهد.


import { Client } from "@modelcontextprotocol/sdk/client";

async function retryWithBackoff<T>(
  fn: () => Promise<T>,
  maxRetries: number = 3,
  baseDelayMs: number = 100
): Promise<T> {
  let lastError: Error | null = null;

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error as Error;

      // بررسی اینکه آیا خطا قابل تلاش مجدد است (مثلاً انقضای مهلت زمانی شبکه)
      if (!isRetryableError(error)) {
        throw error;
      }

      const delay = baseDelayMs * Math.pow(2, attempt);
      const jitter = Math.random() * 100;
      await new Promise(resolve => setTimeout(resolve, delay + jitter));
    }
  }
  throw new Error("Max retries exceeded") from lastError;
}

تفکیک بین خطاهای قابل تلاش مجدد (انقضای مهلت زمانی شبکه، کدهای وضعیت 503) و خطاهای غیرقابل تلاش مجدد (400 درخواست نامعتبر، خطاهای دستوری) حیاتی است. تلاش مجدد برای درخواست‌های نامعتبر فقط منابع را هدر می‌دهد.

الگوی مدارشکن (Circuit Breaker)

تنها تلاش مجدد کافی نیست اگر سرور خاموش باشد. اگر سرور در دسترس نباشد، تلاش مجدد فقط شکست را به تأخیر می‌اندازد. اینجاست که الگوی مدارشکن درخشش می‌کند. این الگو سلامت اتصال را پایش می‌کند و مدار را «باز» می‌کند تا از ارسال درخواست‌های بیشتر برای مدت مشخصی جلوگیری شود. این کار به سرور زمان بازیابی می‌دهد و از غرق شدن کلاینت در درخواست‌های معلق جلوگیری می‌کند.

یک مدارشکن معمولاً سه حالت دارد:

  1. بسته (Closed): عملیات عادی. درخواست‌ها عبور می‌کنند. شکست‌ها شمارش می‌شوند.
  2. باز (Open): درخواست‌ها بلافاصله رد می‌شوند. این حالت برای یک دوره «مهلت زمانی» ادامه دارد.
  3. نیمه‌باز (Half-Open): پس از مهلت زمانی، تعداد محدودی درخواست آزمایشی مجاز است. اگر موفق شوند، مدار بسته می‌شود؛ در غیر این صورت، دوباره باز می‌شود.

class CircuitBreaker {
  private state: "closed" | "open" | "half-open" = "closed";
  private failureCount = 0;
  private lastFailureTime = 0;

  constructor(
    private failureThreshold: number = 5,
    private resetTimeoutMs: number = 10000
  ) {}

  async execute<T>(fn: () => Promise<T>): Promise<T> {
    if (this.state === "open") {
      if (Date.now() - this.lastFailureTime > this.resetTimeoutMs) {
        this.state = "half-open";
      } else {
        throw new Error("Circuit breaker is open");
      }
    }

    try {
      const result = await fn();
      this.handleSuccess();
      return result;
    } catch (error) {
      this.handleFailure();
      throw error;
    }
  }

  private handleSuccess() {
    this.failureCount = 0;
    this.state = "closed";
  }

  private handleFailure() {
    this.failureCount++;
    this.lastFailureTime = Date.now();

    if (this.failureCount >= this.failureThreshold) {
      this.state = "open";
    }
  }
}

ترکیب استراتژی‌ها برای حداکثر مقاومت

کلاینت‌های MCP مقاوم‌تر هر دو الگو را ترکیب می‌کنند. مدارشکن اتصال را در بر می‌گیرد و منطق تلاش مجدد اجرای تک‌تک درخواست‌ها را در حالت‌های بسته یا نیمه‌باز در بر می‌گیرد. این تضمین می‌کند که بدون تحت فشار قرار دادن زیرساختی که در حال شکست است، از شکست‌های گذرا بازیابی کنیم.


const breaker = new CircuitBreaker();
const safeCall = async (request: any) => {
  return breaker.execute(() => retryWithBackoff(() => client.send(request)));
};

نتیجه‌گیری

با رشد پذیرش MCP، پیچیدگی سیستم‌های هوش مصنوعی توزیع‌شده افزایش خواهد یافت. پیاده‌سازی مدارشکن‌ها و منطق تلاش مجدد نه تنها یک بهترین عمل، بلکه یک ضرورت برای ساخت عامل‌های هوش مصنوعی قابل اعتماد و حرفه‌ای است. با مدیریت پیش‌دستانه حالت‌های شکست و مصرف منابع، اطمینان حاصل می‌کنید که کلاینت‌های MCP شما در برابر اختلالات اجتناب‌ناپذیر شبکه و سرویس، پایدار، پاسخگو و مقاوم باقی می‌مانند.

Share: