Model Context Protocol (MCP)

Dayanıklı MCP İstemcileri Oluşturma: Devre Kesicileri ve Tekrar Deneme Mantığını Ustalaşmak

Model Bağlam Protokolü (MCP), yapay zeka ajanlarının dış araçlar ve veri kaynaklarıyla etkileşim kurma biçiminde bir paradigma değişikliğini temsil etmektedir. Bu ajanları dağıtık sistemlerde dağıttıkça, uzak MCP sunucu bağlantılarının güvenilirliği kritik hale gelir. Tek bir geçici ağ arızası veya geçici sunucu aşırı yüklenmesi, doğru şekilde ele alınmazsa tam bir ajan arızasına yol açabilir. Üretim kalitesinde MCP uygulamaları geliştirmek için, basit "try-catch" bloklarının ötesine geçmeli ve devre kesiciler ile akıllı tekrar deneme mantığı gibi gelişmiş dayanıklılık desenlerini uygulamalıyız.

MCP'de Dayanıklılık Neden Önemlidir?

Standart bir MCP mimarisinde, istemci (genellikle LLM ajanı), JSON-RPC üzerinden akışlar aracılığıyla sunucuyla (araç sağlayıcı) iletişim kurar. Yerel fonksiyon çağrılarından farklı olarak, uzak MCP sunucuları ağ gecikmesi, zaman aşımı ve kaynak tükenmesi sorunlarıyla karşılaşabilir. Koruyucu mekanizmalar olmadan, arızalı bir sunucu, istemcinin tüm zaman aşımı bütçesini tüketebilir ve bu da kullanıcıya yönelik gecikmelere veya ölü kilitlere (deadlock) yol açabilir. Dayanıklılık desenleri, belirli bir araç kullanılamaz hale geldiğinde istemcinin yanıt verici kalmasını ve işlevselliği zarif bir şekilde azaltmasını sağlar.

Üstel Geri Çekme ile Tekrar Deneme Mantığını Uygulama

İlk savunma hattı tekrar deneme mantığıdır. Ancak, basit anlık tekrar denemeler, zaten zorlanan bir sunucuyu daha da zorlayabilir. Bunun yerine, rastgele sapmalı (jitter) üstel geri çekme (exponential backoff) kullanmalıyız. Bu strateji, denemeler arasındaki bekleme süresini üstel olarak artırarak sunucudaki yükü azaltır ve geçici sorunların çözülmesine izin verir.


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;

      // Hatanın tekrar denenebilir olup olmadığını kontrol edin (ör. ağ zaman aşımı)
      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;
}

Tekrar denenebilir hatalar (ağ zaman aşımı, 503 durum kodları) ile tekrar deneme yapılamayan hatalar (400 Bad Request, sözdizimi hataları) arasındaki ayrımı yapmak çok önemlidir. Geçersiz istekleri tekrar denemek yalnızca kaynak israfına yol açar.

Devre Kesici Deseni

Sunucu kapalıysa, yalnızca tekrar denemeler yetersiz kalır. Sunucuya ulaşılamıyorsa, tekrar denemeler yalnızca arızayı geciktirir. İşte Devre Kesici (Circuit Breaker) deseni burada devreye girer. Bağlantının sağlığını izler ve belirli bir süre için daha fazla isteği önlemek için devreyi "açık" konuma getirir. Bu, sunucunun toparlanması için zaman tanır ve istemcinin bekleyen isteklerle ezilmesini önler.

Tipik bir devre kesicinin üç durumu vardır:

  1. Kapalı: Normal çalışma. İstekler geçer. Hatalar sayılır.
  2. Açık: İstekler anında reddedilir. Bu durum, bir "zaman aşımı" süresi boyunca devam eder.
  3. Yarı Açık: Zaman aşımından sonra, sınırlı sayıda test isteğine izin verilir. Başarılı olurlarsa devre kapanır; aksi takdirde tekrar açılır.

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";
    }
  }
}

Maksimum Dayanıklılık İçin Stratejileri Birleştirme

En sağlam MCP istemcileri her iki deseni de birleştirir. Devre kesici bağlantıyı sarar ve tekrar deneme mantığı, kapalı veya yarı açık durumlarda bireysel istek çalıştırmasını sarar. Bu, altyapıyı aşırı yüklemeyi önlerken geçici arızalardan kurtulmayı denememizi sağlar.


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

Sonuç

MCP benimsenmesi büyüdükçe, dağıtık yapay zeka sistemlerinin karmaşıklığı artacaktır. Devre kesicileri ve tekrar deneme mantığını uygulamak yalnızca en iyi uygulama değil; güvenilir, profesyonel düzeyde yapay zeka ajanları oluşturmak için bir zorunluluktur. Arıza durumlarını ve kaynak tüketimini proaktif olarak yöneterek, kaçınılmaz ağ ve hizmet kesintilerine karşı MCP istemcilerinizin kararlı, yanıt verici ve dayanıklı kalmasını sağlarsınız.

Share: