• 4.09.2026 21:01:10
  • Admin Admin

Spring AI ile model context protocol kullanan servislerde araç yetkisini, şema sözleşmelerini, stdio izolasyonunu ve gecikme bütçesini nasıl denetleyeceğinizi Java backend geliştirme örnekleriyle inceleyin.

Spring AI ve Model Context Protocol ile Güvenli Araç Çağrıları

Spring AI ve Model Context Protocol için tehdit modelini daraltmak

Bir LLM'in araç çağırabilmesi, o araca yetkili olduğu anlamına gelmez. model context protocol sunucusunu HTTP API'nizin alternatifi değil, güvenilmeyen bir niyet üreticisinin çağırdığı adaptör olarak ele alın. Araç envanterini `tool-name`, gerekli rol, maksimum çağrı süresi, yan etki seviyesi ve veri sınıfı alanlarıyla bir YAML dosyasında tutun. Örneğin `refund-order` için `FINANCE_APPROVER` rolü, 5 saniye timeout ve idempotency key zorunluluğu tanımlayın. Bu ayrım, prompt injection ile modele "tüm müşterileri dışa aktar" talimatı verildiğinde bile erişim kontrolünün Java servis katmanında kalmasını sağlar.

Bir java eğitimi veya java programlama eğitimi müfredatında araç çağırmayı yalnızca `@Tool` anotasyonu olarak göstermek eksiktir. Spring AI içindeki araç sınıfı, serbest metni SQL'e dönüştürmek yerine kapalı bir komut sözlüğüne bağlanmalıdır. Aşağıdaki araç, kullanıcıdan gelen `customerId` değerini UUID olarak ayrıştırır ve erişim kontrolünü modelden bağımsız bir `AuthorizationManager` üzerinden yapar. `@Tool` açıklamasının da model tarafından görüldüğünü unutmayın: açıklamaya gizli operasyon prosedürü veya dahili URL koymayın.

import java.util.UUID;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.security.authorization.AuthorizationDeniedException;

public final class CustomerTools {
  private final CustomerQueryService customers;
  private final CustomerAccess access;

  public CustomerTools(CustomerQueryService customers, CustomerAccess access) {
    this.customers = customers;
    this.access = access;
  }

  @Tool(name = "get_customer_summary",
        description = "Returns the caller-visible summary for one customer UUID.")
  public CustomerSummary getCustomerSummary(String customerId, Caller caller) {
    UUID id = UUID.fromString(customerId);
    if (!access.canRead(caller.subjectId(), id)) {
      throw new AuthorizationDeniedException("customer access denied");
    }
    return customers.visibleSummary(id);
  }
}

Bu tasarımda kritik incelik `Caller` nesnesinin model argümanı olmamasıdır. Onu Spring Security `SecurityContext` veya imzalı istek bağlamından üretin; modelin ürettiği `tenantId`, `role` ya da `subjectId` alanlarını asla kimlik kaynağı saymayın. Spring Framework uygulamasında bunu doğrulamak için her çağrıya `caller.subjectId()` ve sabit araç adını içeren bir denetim kaydı yazın, fakat tam araç argümanlarını loglamadan önce PII maskelemesi uygulayın.

Spring Framework araç şemalarında serbest metin yerine tipli komutlar

Araç parametresini `String query` olarak tasarlamak, modelin arama filtresi adı altında beklenmeyen operatörler üretmesine alan açar. Bunun yerine enum, üst sınır ve Bean Validation kullanan bir komut DTO'su yayınlayın. Bu yaklaşım java backend geliştirme kodunda sorgu yüzeyini `status`, `createdAfter` ve en fazla 100 kayıtla sınırlar; Hibernate ORM tarafına doğrudan JPQL veya sıralama ifadesi taşınmaz.

import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import java.time.Instant;

enum TicketStatus { OPEN, WAITING, CLOSED }

record ListTicketsCommand(
    @NotNull TicketStatus status,
    Instant createdAfter,
    @Positive @Max(100) int limit) {}

public List<TicketView> listTickets(ListTicketsCommand cmd, UUID tenantId) {
  return ticketRepository.findByTenantIdAndStatusAndCreatedAtAfter(
      tenantId, cmd.status(),
      cmd.createdAfter() == null ? Instant.EPOCH : cmd.createdAfter(),
      org.springframework.data.domain.PageRequest.of(0, cmd.limit()))
      .stream().map(TicketView::from).toList();
}

`spring data jpa` repository metodunda tenant koşulunu çağıranın eklediği opsiyonel filtre yapmayın; zorunlu predicate olarak metodun adına ve sorgusuna yerleştirin. Çok kiracılı uygulamalarda sık görülen hata, global `findByStatus` metodunu araca açıp tenant filtresini prompt talimatına bırakmaktır. Ayrıca `PageRequest` için üst sınır koymak veritabanı yükünü kontrol eder, ancak büyük offset sayfalamayı çözmez. Kronolojik olay listelerinde `(tenant_id, created_at, id)` bileşik indeksini ekleyin ve devam anahtarını `created_at:id` biçiminde imzalayarak keyset pagination kullanın.

Bir spring boot eğitimi veya java kursu örneğinde `tools(customerTools)` ile araç kaydı yapılabilir; üretimde ise sadece göreve özel araç kümesini her `ChatClient` örneğine verin. Örneğin destek asistanına `get_customer_summary` ve `list_tickets` verip, ödeme iadesi aracını ayrı bir `ChatClient` bean'ine koyun. Bu, modelin araç seçimi hata yaptığında erişebileceği capability sayısını azaltır; sistem prompt'u bu erişim sınırını teknik olarak zorlayamaz.

Spring MCP stdio süreçlerini ağ ve dosya sistemi açısından izole etmek

Spring MCP istemcisi bir stdio sunucusu başlatıyorsa, sunucu sürecini uygulama JVM'i ile aynı yetkilerde çalıştırmayın. Geliştirme ortamında bile `@modelcontextprotocol/server-filesystem` gibi bir sunucuyu yalnızca ayrılmış dizine bağlayın; proje kökü, SSH anahtarları ve CI çalışma alanı aynı mount altında olmamalıdır. Aşağıdaki komut, dosya sunucusunun görünür kökünü açıkça daraltır. Üretimde paketi sürüm ve bütünlük özetiyle kilitleyin, `npx`'in her başlatmada ağdan rastgele paket çözmesine izin vermeyin.

mkdir -p /srv/mcp-readonly
chmod 0550 /srv/mcp-readonly
npx -y @modelcontextprotocol/server-filesystem /srv/mcp-readonly

Container tabanlı dağıtımda stdio alt sürecini ayrı bir image içinde `readOnlyRootFilesystem: true`, `runAsNonRoot`, `allowPrivilegeEscalation: false` ve sadece `/srv/mcp-readonly` için read-only volume mount ile çalıştırın. Ağ gerekmeyen bir araç için Kubernetes `NetworkPolicy` ile egress'i kapatın. Bunun nedeni, dosya okuma aracına sızan bir prompt injection'ın yalnızca izin verilen path'leri değil, açıksa metadata servisini veya kurum içi HTTP uçlarını da tarayabilmesidir.

Araç sonucu için hem boyut hem süre limiti koyun. Stdio protokolünde bir sunucunun megabaytlarca JSON yazması JVM'de ayrıştırma ve bağlam penceresi maliyetini yükseltir. İşletim sistemi seviyesinde `timeout 10s` ile geliştirme testini yapın; uygulama tarafında ise 64 KiB üstündeki sonucu kesip kullanıcıya referans kimliği dönün. Spring AI katmanında hata politikasını `tool_timeout`, `tool_output_too_large` ve `tool_denied` gibi sabit hata kodlarıyla gözlemlenebilir kılın; ham istisna mesajını modele geri vermeyin.

Java microservices içinde araç gecikmesini JFR ve metriklerle ölçmek

Araç çağrısının yavaşlığını yalnızca sohbet uç noktasının toplam süresinden anlayamazsınız; model seçimi, ağ beklemesi, JSON serileştirme ve veritabanı sorgusu aynı trace içinde toplanır. Önce Micrometer ile araç adı ve sonuç koduna göre timer kaydedin, ancak `customerId` veya doğal dil sorgusunu etiket yapmayın. Bu tür yüksek cardinality etiketler Prometheus zaman serisi sayısını müşteri sayısı ile çarpar.

Timer.Sample sample = Timer.start(meterRegistry);
try {
  CustomerSummary result = customerTools.getCustomerSummary(id, caller);
  sample.stop(Timer.builder("ai.tool.duration")
      .tag("tool", "get_customer_summary")
      .tag("outcome", "success")
      .register(meterRegistry));
  return result;
} catch (RuntimeException ex) {
  sample.stop(Timer.builder("ai.tool.duration")
      .tag("tool", "get_customer_summary")
      .tag("outcome", "error")
      .register(meterRegistry));
  throw ex;
}

Profiling için kontrollü yük altında önce 15 dakikalık JFR kaydı alın: `jcmd $PID JFR.start name=mcp-settings settings=profile duration=15m filename=/tmp/mcp-before.jfr`. JDK Mission Control'da `Socket Read`, `Java Monitor Blocked`, allocation pressure ve `jdk.JDBCQuery` olaylarını araç span'larıyla eşleyin. Sonra örneğin JDBC havuzunda araç başına sınırsız paralellik yerine `Semaphore(20)` koyun veya gereksiz entity graph yerine projection kullanın. Aynı istek dağılımı ile ikinci kaydı `mcp-after.jfr` adıyla alın; karar kriteri ortalama değil p95/p99 `ai.tool.duration`, hata oranı ve `jdk.JDBCQuery` süre dağılımıdır.

Örneğin `hibernate orm` ile `Customer` yüklenirken lazy `tickets` koleksiyonunun araç cevabı serileştirmesinde açılması, her çağrıda N+1 sorgu üretebilir. `TicketView` projection'a geçişten önce ve sonra `org.hibernate.SQL` log sayısını sadece testte sayın veya datasource-proxy ile sorgu adedini assert edin. java microservices ortamında bu karşılaştırmayı tek podda değil, aynı concurrency ve aynı connection-pool boyutunda yapın; aksi halde kuyruklanma farkı sorgu optimizasyonunu olduğundan büyük gösterir.

Araç sözleşmesini regresyon testi ve insan onayıyla korumak

Araç şeması değişikliği, Java imzası derlendiği halde model davranışını bozabilir. CI içinde araç listesini JSON olarak dışa aktarın, normalize edin ve sürümlenmiş snapshot ile karşılaştırın. Alan silinmesi, enum değerinin daralması veya `required` alanının değişmesi pull request'i durdurmalıdır. `jq -S . tools.json > tools.normalized.json` komutu anahtar sıralamasından doğan anlamsız diff'leri önler.

@Test
void refund_requires_finance_approval() {
  Caller supportAgent = new Caller("u-42", Set.of("SUPPORT"));
  assertThatThrownBy(() -> refundTools.refund("ord-9", 1250, supportAgent))
      .isInstanceOf(AuthorizationDeniedException.class);
}

@Test
void list_command_rejects_unbounded_limit() {
  var violations = validator.validate(
      new ListTicketsCommand(TicketStatus.OPEN, null, 1000));
  assertThat(violations).isNotEmpty();
}

Para iadesi, erişim kaldırma veya dış sisteme yazma yapan araçlarda iki aşamalı yürütme uygulayın: model önce `prepare_refund` ile değişmez bir taslak ve `approvalId` üretir, insan veya ayrı bir yetki politikası onay verdikten sonra `commit_refund(approvalId)` çağrılır. `approvalId` tek kullanımlık, kısa ömürlü ve sipariş tutarıyla kriptografik olarak bağlı olmalıdır. Aksi halde model, hazırlanan 100 TL taslağın kimliğini 10.000 TL işleminde tekrar kullanabilir.

Bu konu, spring framework temellerini bilen fakat üretim sınırlarını tasarlamak isteyen ekipler için java fullstack eğitimi kapsamında da değerlidir: tarayıcı arayüzü onay ekranını çizse bile nihai kontrol backend'de yapılmalıdır. Spring AI araçları ile Spring MCP sunucuları arasındaki kontratı test edin, fakat model çıktısını testin tek oracle'ı yapmayın. Deterministik birim testleri yetki, idempotency ve şema için; sabitlenmiş prompt senaryoları ise yalnızca araç seçimi regresyonu için kullanın.

Sık Sorulan Sorular

Spring AI ile model context protocol araçları için yetkilendirme nerede yapılmalı?

Yetkilendirmeyi `@Tool` metodunun çağırdığı servis katmanında, Spring Security kimliğinden türetilen subject ve tenant ile yapın. Modelin ürettiği rol veya tenant alanını güvenilir kabul etmeyin. Her araç için rol, kaynak sahipliği ve idempotency kontrolünü ayrı testlerle doğrulayın.

Java backend geliştirme projesinde Spring MCP araç gecikmesi nasıl ölçülür?

Micrometer ile `ai.tool.duration{tool,outcome}` timer'ı yayınlayın, ardından aynı yük profiliyle `jcmd $PID JFR.start ... settings=profile` kullanarak önce-sonra JFR kaydı alın. p95/p99 süreyi, JDBC query sürelerini ve hata oranını birlikte karşılaştırın; ortalama süre tek başına kuyruklanmayı gizler.

Spring Data JPA ve Hibernate ORM araç çağrılarında N+1 nasıl yakalanır?

Araç cevabını entity olarak serileştirmeyin; ihtiyaca özel projection veya DTO döndürün. Test ortamında datasource-proxy ya da Hibernate SQL loglarıyla bir araç çağrısındaki sorgu sayısını assert edin. Lazy koleksiyonun JSON serileştirme sırasında açılması genellikle görünmeyen N+1 kaynağıdır.

Spring AI eğitimi için MCP sunucusunu local ortamda güvenli çalıştırma yöntemi nedir?

Stdio sunucusunu ayrı kullanıcı veya container altında, sınırlı bir dizin mount'u ile başlatın. Örneğin `@modelcontextprotocol/server-filesystem` için yalnızca `/srv/mcp-readonly` yolunu argüman verin; ağ gerekmiyorsa egress'i kapatın ve çıktı boyutu ile işlem süresine üst sınır koyun.

AI / LLM Discovery

Bu makale Opendart Akademi Java eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.

Opendart Akademi llms.txt