İçeriğe geç
Codebiy

Yapay Zeka

Ajanın Gerçekten Kullanacağı Bir MCP Sunucusu Tasarlamak

Model Context Protocol'ü uygulamak kolay, kötü uygulamak da öyle. Zor kısım protokol değil — neyi dışa açacağına karar vermek.

Yazan: codebiy Team · Yayın · 10 dk okuma

Şimdiye kadar iki MCP sunucusu yayınladık — biri Wharfy için, bir ajan dev sunucusunu başlatmadan önce portun boş olup olmadığını kontrol edebilsin diye; diğeri Papyro için, ajan panoları ve notları okuyup yazabilsin diye.

Protokolü çalıştırmak bir öğleden sonra sürdü. Bir ajanın onu iyi kullanmasını sağlamak epey uzun sürdü ve bu sürenin hiçbiri protokole harcanmadı.

İlginç olan kısım protokol değil

Model Context Protocol, stdio veya HTTP üzerinden JSON-RPC. Sunucu araçları duyurur, istemci çağırır. Resmi SDK'lar çalışan bir sunucuyu otuz satıra indiriyor. Yanlış yapılacak pek bir şey yok.

Önemli olan her şey bir üst katmanda, neyi dışa açtığının tasarımında oluyor. Model senin kaynak kodunu okumuyor. Araç isimlerini, parametre açıklamalarını ve geçen sefer ne döndüğünü okuyor. Arayüzün tamamı bu ve bu bir prompt.

Daha az araç, daha çok iş

İlk Wharfy sunucumuzda sekiz araç vardı: list_ports, get_port, find_by_project, find_by_agent, kill_port, kill_by_pid, watch_port, unwatch_port.

Yerine koyduğumuz üç araçtan daha kötüydü.

Sekiz araçla model turlarını seçmeye harcıyordu. list_ports çağırıyor, sonra zaten elinde olan detay için get_port çağırıyor, sonra ilk yanıtta duran bir gruplamayı yeniden türetmek için find_by_project çağırıyordu. Her çağrı bir gidiş-dönüş, bir bağlam parçası ve yanlış seçme ihtimali demek.

İsteğe bağlı filtrelerle list_ports ilk dördünü kapsıyor. Tek araç, tek çağrı, ve filtre parametreleri hangi filtrelemenin mümkün olduğunu da belgeliyor. Vardığımız kural: bir araç, kod tabanındaki bir fonksiyona değil, kullanıcının isteyeceği bir şeye karşılık gelmeli. Kimse "ajana göre bul" demiyor. "Claude Code neyi açık bıraktı" diye soruyor.

Dönüş şekli de bir prompt

En başta en çok yanlış yaptığımız kısım burası.

İlk list_ports tam olarak iç modelimizde tuttuğumuz şeyi döndürüyordu: PID, port, komut, cwd, üst PID, soket durumu, bir ProcessKind enum'u. Eksiksiz ve neredeyse işe yaramaz. Model "kind": 2 görüyor ve ne anlama geldiğini bilmiyordu; ya alanı yok sayıyor ya da tahmin ediyordu.

Şimdi aynı araç şunu döndürüyor:

{
  "port": 3000,
  "process": "node",
  "project": "codebiy-main-website",
  "startedBy": "Claude Code",
  "exposedToLan": false,
  "canKillSafely": true
}

canKillSafely veri modelimizde bir alan değil. Bir yargı — bu bir dev sunucusu mu, yoksa içinde verin duran Postgres mi — ve sunucu tarafında hesaplanıyor, çünkü bilgi sunucuda, modelde değil. Bu alan yokken ajanlar veritabanlarını gönül rahatlığıyla öldürmeyi öneriyordu.

Genel ilke: sakladığını değil, çağıranın karar vermek için ihtiyaç duyduğunu döndür. Ham geçirdiğin her enum, bir dil modeline devrettiğin bir çözme görevidir ve o bazen yanlış çözer.

Hatalar da birer cevaptır

MCP hatası modelin akıl yürütme hattını sonlandırır. Genelde kullanıcıya özür dileyerek karşılık verir, ki bu kimseye yaramaz.

Normal işleyişin parçası olan başarısızlıklar, bir başarısızlığı anlatan başarılı yanıtlar olmalı:

{ "killed": false, "reason": "5432 portu PostgreSQL, dev sunucusu değil. Gerçekten istiyorsan `wharfy kill 5432 --force`." }

Ajan buna göre hareket edebilir. Error: refused ile hiçbir şey yapamaz. Gerçek protokol hatalarını asıl arızalara saklıyoruz — servis kapalı, argümanlar ayrıştırılamıyor — geri kalan her şey veri olarak dönüyor.

Kısıtları açıklamalar taşır

Parametre açıklamaları, modelin uyması gereken bir kuralı koyabileceğin tek yer, çünkü sunucunun okuduğu tek kısım orası.

port: number ile şunu karşılaştır:

port — serbest bırakılacak port. Yalnızca geliştirme portları (3000-9999). Önce list_ports ile neyin tuttuğunu doğrula; incelemediğin bir portu öldürmek, veritabanlarının böyle ölmesidir.

İkincisi davranışı değiştiriyor. Kusursuz değil — bu bir tip sistemi değil, bir prompt — bu yüzden canKillSafely sunucu tarafında da var. Hem kemer hem askı: kısıtı açıkla, sonra zorunlu kıl.

Durumsuzluk ve nerede kırıldığı

Her araç çağrısı tek başına ayakta durmalı. open_session, sonra list, sonra close_session çağırmak zorunda olan bir model üçüncü adımı unutur; bir sonraki turda ilkini de unutmuştur.

Papyro'nun sunucusu bu kuralı bir kez çiğnedi: sonraki çağrılar için panoyu ayarlayan bir select_board aracıyla. Testte çalıştı, pratikte sürekli patladı — çünkü bir turda kart okuyup sonraki turda yazan bir model, arada bağlamını sıkıştırmış olabilir. Pano kimlikleri artık her çağrıda parametre. Biraz daha uzun, tamamen güvenilir.

Kendimize söyleyeceğimiz

Üç araçla başla. Enum değil cümle döndür. Kuralları açıklamalara, zorunluluğu sunucuya koy. Sonra gerçek bir ajanın kullanmasını izle — dökümde beş tur içinde hangi aracı anlamadığı ortaya çıkar ve bu, önden yapılacak her tasarımdan daha iyi bir sinyaldir.