Scheduler'ınıza Soru Sormak — Milvaion MCP Server
Serinin ilk yazısında Milvaion'ın çıkış noktasını, ikinci yazıda mevcut Hangfire/Quartz kurulumlarını değiştirmeden nasıl izleyebileceğini anlatmıştım.
Bu yazı, sisteme en son eklediğim ve açıkçası en çok tartıştığım özellik hakkında: MCP server.
Baştan uyarayım — bu bir "yapay zeka ile her şey çözüldü" yazısı değil. Aksine, bu özelliği yazarken en çok uğraştığım kısım ne yapabileceği değil, ne yapamayacağıydı.
Aynı Veriye İkinci Bir Kapı
İlk yazıda şunu söylemiştim: asıl değer job'ları çalıştırmakta değil, çalıştıktan sonra ne olduğunu görebilmekte. Dashboard tam da bunun için var ve üzerine en çok vakit harcadığım kısım.
MCP server onun yerine geçen bir şey değil. Dashboard hâlâ sistemi yönettiğin, workflow'unu kurduğun, job'ını düzenlediğin, canlı log akışını izlediğin yer — ve öyle kalıyor. MCP'nin yaptığı, aynı veriye ikinci bir kapı açmak: bakmak yerine sormak istediğin anlar için.
Aradaki fark şurada beliriyor. Dashboard bir soruyu cevaplamakta çok iyi. Bir teşhis ise genelde tek soru değil, birbirini tetikleyen bir soru zinciri:
Gece daily-invoice-export patlamış. Occurrence ekranında exception'ı okuyorsun — bir timeout. Sonra "bu ne zamandır böyle?" diye geçmişe bakıyorsun, salıdan beri. Sonra "salı günü worker ayakta mıydı?" diye worker ekranına, sonra "salı günü biri bu job'a dokunmuş mu?" diye activity log'a geçiyorsun.
Ekranların her biri sana doğru cevabı veriyor. Zorlanan taraf sen oluyorsun: dört ekrandan topladığın parçaları kafanda birleştirmek. Dördüncüde, birincide ne gördüğünü hatırlamaya çalışıyorsun.
Oysa aklındaki soru en baştan beri tek cümleydi: "daily-invoice-export salıdan beri patlıyor, logları oku ve ne değiştiğini söyle."
MCP server bu cümleyi doğrudan çalıştırılabilir hale getiriyor. Gezinmenin kendisini değil, gezinirken bağlamı taşıma yükünü ortadan kaldırıyor.
Kimin ne zaman hangi kapıyı kullanacağı da net: bir şeyi değiştirecekseniz dashboard, bir şeyi anlayacaksanız ikisi de iş görür — hangisi o an daha hızlıysa.
Kurulum
1. Read-only bir API key üret
Dashboard'dan bir API key oluştur ve sadece şunları ver:
ScheduledJobManagement.List,ScheduledJobManagement.DetailFailedOccurrenceManagement.ListWorkerManagement.ListWorkflowManagement.List
Bu setle asistan her şeyi inceleyebilir, hiçbir şeyi değiştiremez. Çoğu insan için doğru varsayılan bu.
2. Sunucuyu editörüne tanıt
Milvaion remote HTTP bir MCP server, yani streamable HTTP transport destekleyen her client bağlanabilir. Claude Code'da dosya düzenlemeye bile gerek yok:
claude mcp add --transport http milvaion \
https://milvaion.sirketiniz.com/mcp \
--header "X-ApiKey: $MILVAION_API_KEY"
Cursor için .cursor/mcp.json:
{
"mcpServers": {
"milvaion": {
"type": "http",
"url": "https://milvaion.sirketiniz.com/mcp",
"headers": { "X-ApiKey": "your-api-key" }
}
}
}
Burada can sıkıcı bir gerçek var: her client aynı şeyi farklı anahtar isimleriyle istiyor. VS Code/Copilot üst seviye anahtar olarak mcpServers değil servers bekliyor. Windsurf url değil serverUrl diyor. Gemini CLI ise httpUrl.
"Bağlandı ama hiç tool görünmüyor" şikayetlerinin bir numaralı sebebi bu. İkinci sebebi ise Copilot'ta chat'in Agent moduna alınmamış olması — Ask modunda MCP tool'ları hiç görünmüyor.
Detaylı client matrisi ve Claude Desktop/ChatGPT'nin kendine has kısıtları için dökümanda ayrı bir bölüm var.
Anahtarı repoya koyma. Yukarıdaki client'ların hepsi ya ortam değişkeni genişletmeyi ya da çalışma anında sorma özelliğini destekliyor. İçinde canlı key olan bir config dosyası er ya da geç commit'lenir ve git geçmişine giren bir key'in tek çaresi yenisini üretip eskisini iptal etmektir.
3. Bir şey sor
Dün gece hangi job'lar patladı?
daily-invoice-exportsalıdan beri patlıyor. Logları oku ve ne değiştiğini söyle.
SendReportJob'ı çalıştırabilecek ayakta bir worker var mı?
Pratikte Neye Benziyor?
İlk soruyu sorduğunda asistan tek bir tool çağırmıyor. Tipik akış şöyle:
list_failures ile dead letter kayıtlarını çekiyor → aynı job'ın birden fazla kaydını görüp list_occurrences ile geçmişe bakıyor → patlamanın ne zaman başladığını buluyor → get_occurrence ile o çalışmanın loglarını ve exception detayını alıyor → list_workers ile worker'ın o sırada ayakta olup olmadığını kontrol ediyor → list_activity_logs ile o tarihte birinin job'a dokunup dokunmadığına bakıyor.
Yani dashboard'da ekran ekran gezerek yaptığın zinciri, sırayla kendisi kuruyor. Farkı, bağlamı kafanda taşımak zorunda olmaman.
Şunun altını çizeyim: bu, sihirli bir teşhis değil. Asistan logda ne yazıyorsa onu okuyor — dashboard'da senin gördüğün veriyle birebir aynı veri. Ama "bu exception'ı ilk gördüğüm tarih" ile "o tarihte activity log'da ne var" arasındaki bağlantıyı kurmak, sabah 9'da kahvesini içmemiş bir insandan daha hızlı yaptığı bir iş.
Cevabı aldıktan sonra genelde yine dashboard'a geçiyorsun zaten — çünkü job'ı düzeltmek, cron'u değiştirmek ya da tekrar tetiklemek orada yapılıyor.
Tool'lar ve İzin Modeli
40'tan fazla tool var ve hepsi bir izne bağlı. Kabaca gruplar:
| Grup | Örnekler |
|---|---|
| Okuma | get_overview, list_jobs, get_job, list_occurrences, get_occurrence, list_failures, list_workers, search_logs, summarize_logs, get_latest_report, list_activity_logs |
| Sistem | get_system_health, get_queue_stats, get_database_statistics, get_configuration |
| Çalıştırma | trigger_job, cancel_occurrence, set_job_active, trigger_workflow |
| Düzenleme | create_job, update_job, resolve_failures |
| Silme | delete_job, delete_occurrences, delete_failures, delete_worker |
Buradaki tasarım kararı şu: hangi tool'ların görüneceğini sen seçmiyorsun, key'in ne yapabileceğini seçiyorsun.
Sadece List ve Detail izni verdiğin bir key'le asistanın önünde on beş okuma tool'u kalıyor, gerisi yok. İzni olmayan bir tool'u çağırdığında dönen hata da eksik iznin adını söylüyor — böylece model körlemesine tekrar denemek yerine sana "şu izni vermen lazım" diyebiliyor.
Bir de fark edilmesi zor ama önemli bir detay: get_occurrence logun tamamını değil, varsayılan olarak son 100 satırını döndürüyor (logLines ile artırabiliyorsun). Döngü içinde log basan bir job, aksi halde asistanın tüm context'ini tek çağrıda doldurabilir.
Prompt'lar — Beklediğimden Daha Önemli Çıktı
Sunucuyla birlikte üç tane prompt template geliyor:
| Prompt | Ne yapıyor |
|---|---|
diagnose_job | Patlayan bir job'ı sırayla geziyor: failure'lar, örüntü, loglar, worker sağlığı, sonra son konfigürasyon değişiklikleri |
overnight_review | Belirli bir zaman aralığını inceleyip hataları tek tek listelemek yerine sebebe göre grupluyor |
explain_workflow | Bir workflow'un adımlarını, dallanmasını ve veri akışını düz cümleyle anlatıyor |
Bunlar ilk bakışta "hazır soru şablonu" gibi duruyor ama işlevleri farklı. Kendi haline bırakıldığında model genelde ilk okuduğu tool'dan başlıyor — list_jobs çağırıp job listesini geziyor, oysa sorunun cevabı list_failures'ta.
Prompt'lar modelin önüne bir çalışma sırası koyuyor. Aradaki fark, doğru cevap ile üç fazladan tool çağrısı sonrası doğru cevap arasındaki fark.
Bilinçli Olarak Dışarıda Bıraktıklarım
Kullanıcı yönetimi, roller, izinler, API key'ler, dispatcher kontrolü ve konfigürasyon yönetimi için hiç tool yok. Key'e ne yetki verirsen ver, bunlara MCP üzerinden erişilemiyor. Sebep basit: bir asistanın yeni bir API key üretebilmesi ya da kendi izinlerini genişletebilmesi, düşünmek bile istemediğim bir senaryo.
Workflow oluşturma ve düzenleme de yok. Condition'ları ve data mapping'leri olan yönlü bir grafiği tool çağrılarıyla kurmak teknik olarak mümkün ama doğru yer görsel builder. Bir asistanın "sanırım bu iki node'u bağlamak istedin" tahmini üzerine kurulmuş bir workflow, kimsenin production'da istemeyeceği bir şey.
Birkaç güvenlik detayı daha:
- Yan etkiler kime ait olduğu yazılarak kaydediliyor. MCP üzerinden tetiklenen, iptal edilen ya da çözüldü işaretlenen her şey key'in adıyla loglanıyor. Geçmişte "bunu insan mı yaptı asistan mı" sorusunun cevabı duruyor.
- Concurrency politikaları asla atlanmıyor.
trigger_jobher zaman force kapalı çalışıyor. Bir job'ın eşzamanlılık politikasını ezmek bilinçli bir insan eylemi olarak dashboard'da kalıyor. - Alanlar kazara temizlenemiyor.
update_jobsadece kendisine verilen argümanları değiştiriyor; verilmeyen alan boşaltılmıyor, olduğu gibi bırakılıyor.
Ve dürüst olmam gereken kısım:
Tool açıklamaları modele "bunu yapmadan önce onay al", "delete_job yerine set_job_active tercih et" diyor. Ama bunlar prompt, garanti değil. Modelin bunlara uyacağına dair bir taahhüt veremem.
Bu yüzden tavsiyem net: Create, Update ve Delete izinlerini, aynı yetkiyi bir insana dashboard üzerinden vermekten rahatsız olmayacağın yerlerde ver. Trigger gerçek işin çalışmasına sebep olur. Delete geri alınamaz. Geri kalan her durumda doğru cevap read-only bir key.
Çalıştığını Doğrulamak
/mcp HTTP üzerinden JSON-RPC konuşuyor, yani bir client'a hiç bulaşmadan curl ile test edebilirsin:
curl -X POST http://localhost:5000/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "X-ApiKey: $MILVAION_API_KEY" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
Header'ı çıkardığında aynı isteğin 401 dönmesi, endpoint'in korunduğunun en hızlı doğrulaması.
Sunucu stateless modda çalışıyor; herhangi bir API replikası herhangi bir isteği karşılayabiliyor, load balancer arkasında sticky session gerekmiyor. Bunun bedeli, kalıcı session gerektiren sampling ve elicitation gibi özelliklerin desteklenmemesi.
Sonuç
MCP server'ı yazarken kendime sorduğum soru "yapay zekayı nasıl eklerim" değildi. Soru şuydu: kafamda tek cümle olan bir soruyu, gerçekten tek cümlede sorulabilir hale nasıl getiririm?
Cevabın MCP olması biraz da zamanlama meselesi — protokol ortada, ekiplerin çoğunda zaten Claude Code ya da Cursor açık. Milvaion'ın yapması gereken tek şey, verisini bu ekosisteme düzgün bir şekilde açmaktı. Kendi LLM entegrasyonunu yazmak yerine veri kaynağı olmak, hem daha az kod hem daha az sorumluluk.
İşin ilginç yanı şu: bu özelliği yazarken harcadığım zamanın çoğu tool'ları eklemekle değil, hangilerini eklememem gerektiğine karar vermekle geçti. Bir scheduler'a doğal dil arayüzü açmak kolay; onu production'da güvenli tutmak asıl iş.
Milvaion GitHub'da Apache 2.0 lisansıyla açık kaynak. MCP server'ın tüm tool listesi, client konfigürasyonları ve güvenlik modeli için dökümana göz atabilirsin.
Bir sonraki yazıda görüşmek dileğiyle…