← Blog Listesine Dön
2026-08-19

Model Context Protocol (MCP) ve Görünmez Tehlike: %1200 CPU Tüketimi, Yetim Süreçler ve 'Retry Storm' Vaka Analizi

Yapay zekâ ve otonom ajan (agentic AI) geliştirme ekosisteminde Model Context Protocol (MCP), büyük dil modellerini (LLM) harici araçlar, veritabanları, API'lar ve bağlam depoları ile konuşturan evrensel bir standart haline geldi. Ancak her yeni mimari sıçramada olduğu gibi, MCP entegrasyonlarının altında da gözden kaçan tasarım tercihleri, hem yerel geliştirici makinelerinde hem de canlı sunucu altyapılarında ciddi felaketlere davetiye çıkarabiliyor.

Bu vaka analizinde; geliştirme istasyonumuzda hiçbir MCP aracını aktif olarak çağırmadığımız halde işlemcinin tüm çekirdeklerini kilitleyen 12 adet mcp-remote sürecinin anatomisini, problemin kökenindeki mimari kusurları ve bir Remote MCP Server geliştirirken neden "Retry Storm" senaryolarına karşı hazırlıklı olmamız gerektiğini tüm teknik detaylarıyla masaya yatırıyoruz.


1. Belirti: Boştaki Bilgisayar Neden Çıldırır?

Sıradan bir geliştirme gününün sabahında iş istasyonumuzu açtıktan kısa bir süre sonra, fanların maksimum devire ulaştığını, klavyenin ısındığını ve işletim sisteminde arayüz tepkilerinin gecikmeye başladığını fark ettik. Oysa o anda makinede ne ağır bir Docker derlemesi, ne yerel bir model çıkarımı (inference), ne de büyük bir derleme (build) komutu çalışıyordu.

macOS Etkinlik Monitörü (Activity Monitor) açıldığında karşılaştığımız tablo ise son derece düşündürücüydü:

Process Name      | % CPU   | CPU Time | Threads | PID   | User
---------------------------------------------------------------
node (mcp-remote) | 100.1%  | 42:15.32 | 11      | 78421 | aes
node (mcp-remote) | 99.8%   | 41:58.10 | 11      | 78425 | aes
node (mcp-remote) | 100.0%  | 40:02.44 | 11      | 78440 | aes
node (mcp-remote) | 99.6%   | 39:18.89 | 11      | 78452 | aes
node (mcp-remote) | 100.2%  | 38:45.12 | 11      | 78466 | aes
node (mcp-remote) | 99.9%   | 38:12.01 | 11      | 78480 | aes
... (toplam 12 adet süreç)
---------------------------------------------------------------
Toplam CPU Yükü   : > %1200 (12 Çekirdeğin Tamamı Kilitli!)
Sistem Idle       : %1.2

İşin en kritik paradoksu şuydu: O esnada açık olan IDE veya sohbet arayüzlerinde hiçbir MCP aracı çağrılmıyordu. Kullanılmayan, çağrılmayan ve sessizce arka planda durması beklenen araçlar, makinenin tüm donanım kaynaklarını sömüren bir canavara dönüşmüştü.


2. Derinlemesine Teşhis: Terminalde Süreç Ağacı Dedektifliği

Problemin kaynağını tespit etmek için terminale geçerek süreç ağacını (process tree) ve açık ağ soketlerini denetledik:

# Kilitlenen mcp süreçlerini listeleme
ps aux | grep -iE 'mcp-remote|mcp' | grep -v grep

Gelen çıktıda; Cloudflare, Google Stitch, Rill ve Apache Superset gibi uzak/yerel servislere bağlanmaya çalışan npx -y mcp-remote <endpoint> komutlarının dizildiğini gördük.

Ardından bu süreçlerin ebeveyn kimliklerini (PPID - Parent Process ID) inceledik:

# Süreçlerin ebeveyn (PPID) ilişkilerini görüntüleme
ps -ef | grep mcp-remote

Ortaya çıkan mimari tablo şu şekildeydi:

[İşletim Sistemi Çekirdeği / launchd (PID: 1)]
    │
    ├── [IDE Language Server / Arka Plan İstemcisi] ─── (Başlatılan aktif mcp-remote süreçleri)
    │
    └── [Yetim / Orphaned mcp-remote Süreçleri] (PID 1'e devredilmiş, kilitli süreçler)
  1. Aktif İstemci Süreçleri: Süreçlerin bir kısmı IDE'nin arka plan servislerine (language_server veya extension host) bağlıydı.
  2. Yetim (Orphaned/Zombie) Süreçler: Süreçlerin önemli bir kısmı ise IDE çökmesi, yeniden başlatılması veya pencere yenilenmesi sonrasında kapatılmamış; ebeveyni öldüğü için işletim sisteminin 1 numaralı sürecine (launchd) bağlanarak arka planda bağımsız yaşayan zombilere dönüşmüştü.

3. Sistemin Kilitlenmesine Yol Açan 3 Temel Mimari Hata

Bu kriz tesadüfi bir bellek sızıntısı (memory leak) değil; üç farklı mimari tasarım açığının bir araya gelerek birbirini beslemesiyle oluştu.

flowchart TD
    A[IDE Başlatılır / Reload Edilir] --> B[mcp_config.json Okunur]
    B --> C[Eager Startup: 12 Alt Süreç Başlatılır]
    C --> D{Hedef Port / Sunucu Açık mı?}
    D -- Hayır / ECONNREFUSED --> E[mcp-remote: Eksik Backoff / Tight Busy Loop]
    E --> F[%100 CPU Tüketimi / Çekirdek Kilitlenmesi]
    A -.->|IDE Kapatılır / Yeniden Başlatılır| G[Subprocess'e SIGKILL Gitmez]
    G --> H[Yetim Süreç PID 1'e Bağlanır]
    H --> F

A. MCP İstemcilerinin "Önceden Başlatma" (Eager Startup) Davranışı

Birçok modern MCP istemcisi (Antigravity IDE, Cursor, Claude Desktop, VS Code uzantıları), mcp_config.json dosyasında tanımlanan tüm sunucuları kullanıcı o aracı çağırmasa dahi IDE açıldığı anda birer alt süreç (stdio child process) olarak başlatır.

Buradaki amaç, kullanıcı sohbet penceresinde @tool yazdığında sıfır gecikmeyle yanıt vermektir. Ancak bu yaklaşım; kapalı bir yerel port (localhost:8085 gibi) veya ulaşılamayan bir uzak staging sunucusu tanımlı olduğunda, arka planda anında bir bağlantı arayışı ve sonsuz bir hata trafiği başlatır.

B. mcp-remote Kütüphanesindeki Sonsuz Meşgul Döngü (Infinite Busy-Wait / Eksik Backoff)

Kullanılan mcp-remote köprü kütüphanesi, hedef HTTP/SSE uç noktasına bağlanamadığında (örneğin ECONNREFUSED veya ETIMEDOUT aldığında) araya hiçbir bekleme süresi (sleep, setTimeout) koymadan milisaniyede binlerce kez yeniden bağlanmayı (retry) deniyordu.

Bir ağ hatası anında CPU'yu serbest bırakmayan bu "tight busy-loop" yapısı, Node.js event-loop'unu kilitledi ve her bir sürecin tek bir CPU çekirdeğini %100 kapasiteyle işgal etmesine neden oldu. 12 süreç = 12 çekirdek = %1200 CPU tüketimi!

C. "disabled": true Tuzağı ve Süreç Yaşam Döngüsü (Lifecycle) İhmali

Yapılandırma dosyasında test amaçlı eklenen bazı sunucuların altına "disabled": true yazılmıştı:

{
  "mcpServers": {
    "rill": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "http://localhost:8085/sse"],
      "disabled": true
    }
  }
}
MİMARİ UYARI

Çoğu MCP istemcisi standart bir şema denetleyicisine sahip değildir ve Object.keys(config.mcpServers) üzerinden tüm anahtarları sorgusuz sualsiz spawn() eder. "disabled": true parametresi istemci tarafından yok sayıldığı için, geliştirici sunucunun kapalı olduğunu sansa bile arka planda gizlice çalıştırılır.

Üstelik IDE penceresi her yeniden yüklendiğinde (Cmd+R / Developer: Reload Window), eski alt süreçlere düzgün SIGTERM/SIGKILL sinyalleri iletilmediğinden, eski süreçler işletim sisteminde "yetim" kalarak çalışmaya devam etti ve her yeniden başlatmada yeni kilitli süreçler birikti.


4. Dağıtık Sistemler Perspektifi: Bir "Remote MCP Server" Bu Durumda Hayatta Kalabilir mi?

Bu vaka yalnızca bir yerel bilgisayar donma hikayesi değildir. Olayı bir backend mimarı veya platform mühendisi gözüyle tersine çevirelim:

"Eğer hedefteki Remote MCP Server siz olsaydınız ve ağınızdaki binlerce istemci bu şekilde döngüye girseydi ne olurdu?"

Dağıtık sistemler literatüründe bu felakete "Retry Storm" (Yeniden Deneme Fırtınası) veya "Thundering Herd" denir.

sequenceDiagram
    autonumber
    actor Dev as MCP İstemcisi (mcp-remote)
    participant Edge as Cloudflare / API Gateway
    participant Backend as Remote MCP Server (FastAPI/Node)
    
    Note over Dev, Backend: SENARYO 1: Korumasız Arka Uç (Sistem Çöküşü)
    Dev->>Backend: SYN / HTTP Connect (Deneme 1)
    Backend-->>Dev: ECONNREFUSED
    Dev->>Backend: SYN / HTTP Connect (0ms sonra - Deneme 2)
    Dev->>Backend: SYN / HTTP Connect (0ms sonra - Deneme 3)
    Note over Backend: Binlerce soket açıldı! ulimit -n doldu. Event loop kilitlendi. 502/504 Çöküş!
    
    Note over Dev, Backend: SENARYO 2: Korumalı Mimari (Dayanıklı Sistem)
    Dev->>Edge: SYN / HTTP Connect
    Edge->>Backend: Proxy Request
    Backend-->>Edge: 503 Unavailable
    Edge-->>Dev: HTTP 429 Too Many Requests (Retry-After: 30)
    Note over Edge: Rate limit tetiklendi. Arka uç sunucusu güvende!

Korumasız Bir Sunucu Mimarisi (FastAPI / Express / Standart Node.js):

  1. Soket ve File Descriptor İflası: İstemciler aralıksız TCP el sıkışması (handshake) ve HTTP/SSE bağlantısı açmaya çalışır. İşletim sisteminin açık dosya/soket limiti (ulimit -n) ve TCP TIME_WAIT soket havuzları dakikalar içinde tükenir.
  2. Worker Havuzunun Felç Olması: Sunucunun tüm thread veya asenkron event-loop kapasitesi bu hayalet isteklere yanıt vermeye harcanır.
  3. Kendiliğinden Gelişen DoS (Self-Inflicted DoS): Sunucu, gerçek ve meşru kullanıcılara 502 Bad Gateway veya 504 Gateway Timeout döndürmeye başlar. Kendi istemcileriniz, kendi backend'inizi çökertmiş olur.

Korumalı Bir Sunucu Mimarisi (Cloudflare / API Gateway / Nginx):

  1. Rate Limiting & WAF Filtreleme: Akıllı bir API Gateway, aynı IP adresinden veya oturumdan gelen anormal bağlantı patlamasını anında algılar ve isteği arka uca iletmeden HTTP 429 Too Many Requests veya geçici HTTP 403 Forbidden ile keser.
  2. Circuit Breaking (Devre Kesici): Arka uç servisleri belirli bir hata eşiğini aştığında devre kesici devreye girer ve istemcilere Retry-After: 60 başlığıyla net bir bekleme süresi dikte eder.

5. Sorunu Nasıl Çözdük? (Adım Adım İyileştirme)

Adım 1: Kilitlenen Süreçlerin Temizlenmesi

İlk olarak makinede kaynak tüketen tüm yetim ve aktif mcp-remote alt süreçlerini zorla sonlandırdık:

# Tüm mcp-remote süreçlerini tek komutla temizleme
pkill -9 -f "mcp-remote"

Adım 2: Konfigürasyon Dosyasının Ayrıştırılması

mcp_config.json dosyasındaki sahte "disabled": true alanlarına güvenmek yerine, kullanılmayan veya kapalı servislere ait sunucuları mcpServers nesnesinin tamamen dışına taşıyarak disabledMcpServers anahtarı altına aldık:

{
  "mcpServers": {
    "notebooks": {
      "command": "node",
      "args": ["/Users/aes/tools/notebooks/dist/index.js"]
    }
  },
  "disabledMcpServers": {
    "cloudflare": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://api.cloudflare.com/mcp"]
    },
    "superset": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "http://localhost:5008/sse"]
    },
    "rill": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "http://localhost:8085/sse"]
    }
  }
}

Sonuç ve Performans Karşılaştırması

Yapılan müdahale sonrasında sistemin telemetri değerleri anında normale döndü:

Metrik Müdahale Öncesi (Kriz Anı) Müdahale Sonrası (Stabil) Değişim
Toplam CPU Yükü %1200.4 %2.3 - %99.8 İyileşme
İşlemci Boşta (Idle) %1.2 %91.7 + %90.5 Kapasite
Fan Hızı (RPM) 6200 RPM (Maksimum) 0 - 1400 RPM (Sessiz) Sessiz & Normal
Yetim Süreç Sayısı 12 Adet Zombie 0 Adet Sıfır Yetim Süreç

6. MCP Geliştiricileri ve Mimarları İçin Çıkarılacak Dersler (Best Practices)

MCP ekosisteminde ister istemci (client) ister uzak sunucu (server) geliştiriyor olun, mimarinizi şu temel ilkeler üzerine inşa etmelisiniz:

1. Exponential Backoff ve Full Jitter Uygulayın

Asla bir bağlantı koptuğunda milisaniyelik sabit döngülerle yeniden bağlanmaya çalışmayın. Aşağıdaki matematiksel modeli ve kod şablonunu kullanın:

$$t_{\text{wait}} = \min(t_{\text{max}}, t_{\text{base}} \times 2^{\text{attempt}}) + \text{random_jitter}$$

// Güvenli Yeniden Bağlanma (Resilient Reconnect) Fonksiyonu
async function connectWithExponentialBackoff(
  endpoint: string,
  maxAttempts = 10,
  baseDelayMs = 1000,
  maxDelayMs = 30000
) {
  let attempt = 0;

  while (attempt < maxAttempts) {
    try {
      return await establishMcpConnection(endpoint);
    } catch (error) {
      attempt++;
      if (attempt >= maxAttempts) {
        console.error(`[MCP] Bağlantı başarısız oldu (${endpoint}). Yeniden deneme durduruldu.`);
        throw error;
      }

      // Exponential Backoff + Full Jitter Hesabı
      const exponentialDelay = Math.min(maxDelayMs, baseDelayMs * Math.pow(2, attempt));
      const jitter = Math.random() * exponentialDelay;
      const sleepTime = Math.floor(jitter);

      console.warn(`[MCP] Bağlantı koptu. ${sleepTime}ms sonra tekrar denenecek (Deneme: ${attempt}/${maxAttempts})...`);
      await new Promise((resolve) => setTimeout(resolve, sleepTime));
    }
  }
}

2. Process Group (PGID) ve POSIX Sinyal Yönetimi

MCP istemcisi geliştiren ekipler, başlattıkları alt süreçleri (spawned child processes) detached: true veya process group seviyesinde izlemeli; ana pencere kapandığında veya çöktüğünde işletim sistemi sinyallerini (SIGTERM, SIGINT) tüm alt ağaca eksiksiz iletmelidir.

// Temiz Alt Süreç Kapatma (Clean Process Teardown)
const child = spawn('npx', ['-y', 'mcp-remote', url], { detached: false });

process.on('SIGTERM', () => {
  child.kill('SIGTERM');
  process.exit(0);
});

process.on('exit', () => {
  child.kill('SIGKILL');
});

3. Server-Side Savunma: Connection Pool & Rate Limiting

Remote MCP Server geliştiren ekipler:

  • Uç noktalarını mutlaka Cloudflare WAF / API Gateway arkasına almalı.
  • İstemciler için maksimum eşzamanlı SSE (Server-Sent Events) soket limiti koymalı.
  • Bağlantı kopmalarını tespit etmek için periyodik Heartbeat / Ping-Pong çerçeveleri kullanmalıdır.

7. Özet: Ajan Çağında Altyapı Disiplini

Model Context Protocol, yapay zekâ asistanlarını statik birer sohbet botu olmaktan çıkarıp dünyayı değiştiren otonom ajanlara dönüştüren devasa bir köprüdür. Ancak bu köprünün taşıyıcı kolonları; klasik yazılım mühendisliğinin bağlantı yönetimi, hata toleransı, süreç izolasyonu ve hız sınırlama (rate limiting) gibi temel kurallarına sıkı sıkıya bağlıdır.

Ajanlarınızı akıllı kılarken, altyapınızı dayanıklı tutmayı ihmal etmeyin.


Onmartech Tech Lab | Model Context Protocol, AI Agents ve Bulut Sistem Mimarileri İncelemeleri

Önerilen Okumalar

2026-08-31

The Evolution of Metabase AI Assistant: From Naive Text-to-SQL to a 143-Tool Enterprise MCP BI Engine

The engineering journey from a fragile natural language SQL prototype to an enterprise Model Context Protocol (MCP) server featuring dbt semantic layer routing, autonomous self-healing queries, 24-column dashboard layout architecting, and governance-first business memory.

Okumaya Devam Et →
2026-08-19

Model Context Protocol (MCP) and the 'Agentic MarTech' Revolution: Orchestrating the Modern Marketing Stack with AI Agents

The paradigm shift from manual dashboards to autonomous AI agents. How MCP connects BigQuery, Google Ads, GA4, and Meta into an automated marketing operating system—and how to govern cost, quota, and PII risks.

Okumaya Devam Et →