Mert Özen Her Satırda Daha İleri
Yapay Zeka Claude Code — #12

Claude Agent SDK'ya Giriş: Claude Code'u Kütüphane Olarak Kullanmak

Mert Özen 15 Kas 2026 11 dk 3 görüntülenme
Claude Agent SDK'ya Giriş: Claude Code'u Kütüphane Olarak Kullanmak

Claude Agent SDK, Claude Code'un tool'larını, agent loop'unu ve permission sistemini TypeScript ve Python'dan çağırmanı sağlar; her query() ayrı bir Claude Code süreci başlatır. Bookstore için gece çalışan bir bağımlılık raporu agent'ı: settingSources, tools, allowedTools ve dontAsk ile dar yetki, structured output, tur ve bütçe sınırı, env tuzağı ve GitHub Actions ile zamanlama.

Sürüm notu: Bu yazı Claude Code 2.1.325'i (13 Kasım 2026) ve npm'deki @anthropic-ai/claude-agent-sdk 0.3.325 paketini anlatır; ayrıntılar 15 Kasım 2026'da code.claude.com/docs'taki Agent SDK overview, Quickstart, Configure your agent, Configure permissions, Use Claude Code features in the SDK, Get structured output from agents, Track cost and usage, Hosting the Agent SDK, Securely deploying AI agents ve Agent SDK reference - TypeScript sayfalarından kontrol edilmiştir. Bu yazıda resmî belgelerden yararlanılmıştır. Script TypeScript 7.0.2 ile derlenmiş ve Node.js 24'te, Claude Code binary'si yerine bir stub verilerek çalıştırılmıştır; workflow actionlint 1.7.12 ile denetlenmiştir.

Önce Tahmin Et

Emre, sitedeki serilerin ortak örnek uygulaması Bookstore (bir online kitapçı) için her gece bağımlılıkları tarayan küçük bir agent yazdı. API key'i vermek için query()'nin env seçeneğine yalnız ANTHROPIC_API_KEY'i koydu, settingSources'ı boş bıraktı, maxTurns: 5 verdi. Üç şey oldu: Claude dotnet komutunu hiç bulamadı; koşu, repo'daki bookstore-dev plugin'inin hook'larını ve MCP (Model Context Protocol; dış araç ve veri kaynaklarına bağlanma protokolü) server'larını da yüklemeye çalıştı; büyük bir repo'da tur sınırına takılınca script elindekini yazamadan çöktü.

Tahminin ne? dotnet neden bulunamadı, plugin neden yüklendi, script neden rapor yazmadan düştü? Cevaplar seçenekleri tek tek açtığımız bölümde.

Agent SDK Nedir

Claude Agent SDK, Claude Code'un tool'larını, agent loop'unu ve context yönetimini Python ve TypeScript'ten kullanmanı sağlayan bir kütüphane. query() çağırdığında SDK ayrı bir Claude Code süreci başlatıp onunla stdio üzerinden konuşuyor; shell'e, çalışma klasörüne ve session dosyalarına bu alt süreç sahip. Built-in tool'lar, permission sistemi, hook'lar, subagent'lar, session'lar, skill'ler ve plugin'ler aynı. İki SDK de native bir Claude Code binary'si getiriyor; TypeScript tarafında bu binary npm'in optional dependency'leriyle kuruluyor, --omit=optional ile kurarsan gelmiyor.

C# SDK'sı yok. Bu yüzden örneği, Angular tarafında zaten kullandığımız TypeScript ile yazıyoruz. .NET'ten Claude'la agent kurmak istersen Claude API ile Uygulama Geliştirme serisindeki tool runner ve Managed Agents yazıları ya da .NET ile Yapay Zeka serisinin on birinci yazısındaki Microsoft Agent Framework daha doğal seçenekler.

Kimlik doğrulama Claude Console'dan alınan API key ile, ya da Amazon Bedrock, Google Cloud ve Microsoft Foundry ortam değişkenleriyle yapılıyor. Belgeler, önceden onay alınmadıkça üçüncü taraf ürünlerin claude.ai girişi ya da abonelik limitleri sunamayacağını açıkça yazıyor. SDK'nın kullanımı Anthropic'in Commercial Terms of Service'ine bağlı; kendi ürününü "Claude Code" diye adlandıramıyorsun.

Hangi Yaklaşım Ne Zaman

12-agent-yaklasimlari-tr.png

Ayırt edici soru iki tane: dosya okuma, düzenleme ve shell tool'ları ile permission sistemi hazır mı, ve agent loop'u nerede çalışıyor? Claude Code CLI interaktif iş ve tek seferlik claude -p görevleri için. Agent SDK aynı harness'i kendi uygulamanın içinde, senin işlettiğin bir süreçte istediğinde. Claude API'nin client SDK'larında loop'u sen yazıyorsun ya da beta tool runner'a bırakıyorsun; tool'lar senin kodun. Managed Agents'ta harness'i Anthropic barındırıyor, session'lar Anthropic'in cloud sandbox'ında ya da senin altyapındaki bir sandbox'ta çalışıyor. Microsoft Agent Framework ise .NET uygulamasının içinde, tool'ları senin yazdığın bir agent soyutlaması.

Gece raporu için seçim kolay: iş repo'da komut çalıştırmak ve dosya okumak, yani Claude Code'un hazır tool'ları yetiyor; tek ihtiyaç bunu bir script'ten, sınırları belli bir şekilde çağırmak. Tek bir claude -p komutu da işi görürdü; SDK'yı seçme sebebimiz sonucu, hataları ve maliyeti tip güvenli bir kodda işlemek.

Bookstore: Gece Çalışan Bağımlılık Raporu

Agent repo'da tools/dependency-report altında duruyor. İki komutun çıktısını alıp her güvenlik açığını Bookstore'da kullanıldığı dosyayla eşleştiriyor: hangi .csproj paketi referans ediyor, hangi Angular dosyası import ediyor. Komutları çalıştırmayı bir script de yapabilirdi; agent'ın katkısı bu eşleştirme ve kısa açıklama. package.json:

{
  "name": "bookstore-dependency-report",
  "private": true,
  "type": "module",
  "description": "Nightly dependency and vulnerability report for the Bookstore repository, built on the Claude Agent SDK",
  "scripts": {
    "report": "tsx report.ts",
    "check": "tsc --noEmit"
  },
  "engines": {
    "node": ">=24"
  },
  "dependencies": {
    "@anthropic-ai/claude-agent-sdk": "0.3.325"
  },
  "devDependencies": {
    "@types/node": "26.6.6",
    "tsx": "4.23.15",
    "typescript": "7.0.2"
  }
}

Sürümleri sabitledik ve package-lock.json'ı repo'ya koyduk. TypeScript 7.0 yalnız tip kontrolü için (npm run check), çalıştırmayı tsx yapıyor. Kurulum npm ci; optional dependency'leri atlamamak gerekiyor, yoksa native binary gelmiyor.

report.ts:

// Bookstore repo'su için gece çalışan bağımlılık ve güvenlik açığı raporu.
// tools/dependency-report içinden, ANTHROPIC_API_KEY tanımlıyken: npm run report
import { query, type Options, type SDKResultMessage } from "@anthropic-ai/claude-agent-sdk";
import { mkdir, writeFile } from "node:fs/promises";
import path from "node:path";

type Finding = {
  ecosystem: "nuget" | "npm";
  project: string;
  package: string;
  installedVersion: string;
  severity: "low" | "moderate" | "high" | "critical";
  usedIn: string;
  advisory: string;
  fixedIn?: string;
};

type Report = { summary: string; findings: Finding[] };

const repoRoot = path.resolve(import.meta.dirname, "../..");
const reportPath = path.join(repoRoot, "artifacts", "dependency-report.md");

const schema = {
  type: "object",
  properties: {
    summary: { type: "string" },
    findings: {
      type: "array",
      items: {
        type: "object",
        properties: {
          ecosystem: { type: "string", enum: ["nuget", "npm"] },
          project: { type: "string" },
          package: { type: "string" },
          installedVersion: { type: "string" },
          severity: { type: "string", enum: ["low", "moderate", "high", "critical"] },
          usedIn: { type: "string" },
          advisory: { type: "string" },
          fixedIn: { type: "string" },
        },
        required: ["ecosystem", "project", "package", "installedVersion", "severity", "usedIn", "advisory"],
      },
    },
  },
  required: ["summary", "findings"],
};

const prompt = [
  "Produce a dependency vulnerability report for the Bookstore repository.",
  "1. Run `dotnet list Bookstore.slnx package --vulnerable --include-transitive` from the repository root.",
  "2. Run `npm audit --json --prefix web/bookstore-web`.",
  "3. For each vulnerable package, find where Bookstore uses it: the .csproj that references it,",
  "   or the Angular files that import it. Put the file paths in usedIn.",
  "4. Write the advisory in one plain sentence and fill fixedIn when a fixed version is known.",
  "Do not change any file. If a command fails, say so in summary and report what you have.",
].join("\n");

const options: Options = {
  cwd: repoRoot,
  settingSources: [],
  tools: ["Read", "Grep", "Glob", "Bash", "PowerShell"],
  allowedTools: [
    "Bash(dotnet list Bookstore.slnx package --vulnerable *)",
    "Bash(npm audit *)",
    "PowerShell(dotnet list Bookstore.slnx package --vulnerable *)",
    "PowerShell(npm audit *)",
  ],
  disallowedTools: ["Read(**/.env)", "Read(**/.env.*)", "Read(**/appsettings.Production.json)"],
  permissionMode: "dontAsk",
  model: "sonnet",
  maxTurns: 20,
  maxBudgetUsd: 1,
  persistSession: false,
  outputFormat: { type: "json_schema", schema },
};

function toMarkdown(report: Report, result: SDKResultMessage): string {
  const lines = [
    "# Bookstore dependency report",
    "",
    `Generated: ${new Date().toISOString()}`,
    "",
    report.summary,
    "",
    "| Severity | Ecosystem | Package | Version | Fixed in | Used in | Advisory |",
    "| --- | --- | --- | --- | --- | --- | --- |",
    ...report.findings.map((f) =>
      `| ${f.severity} | ${f.ecosystem} | ${f.package} | ${f.installedVersion} | ${f.fixedIn ?? "-"} | ${f.usedIn} | ${f.advisory} |`),
  ];
  if (result.permission_denials.length > 0) {
    lines.push("", "Denied tool calls:", ...result.permission_denials.map((d) => `- ${d.tool_name}: ${JSON.stringify(d.tool_input)}`));
  }
  lines.push("", `Turns: ${result.num_turns}, estimated cost: ${result.total_cost_usd.toFixed(2)} USD`);
  return lines.join("\n") + "\n";
}

let result: SDKResultMessage | undefined;
try {
  for await (const message of query({ prompt, options })) {
    if (message.type === "result") {
      result = message;
    }
  }
} catch (error) {
  // Tur ya da bütçe sınırı koşuyu bir result mesajıyla bitirir, ardından hata fırlatılır.
  console.error(`query ended with an error: ${String(error)}`);
}

if (!result) {
  console.error("No result message.");
  process.exit(1);
}
if (result.subtype !== "success" || result.is_error || !result.structured_output) {
  console.error(`Run failed: ${result.subtype}, estimated cost ${result.total_cost_usd.toFixed(2)} USD`);
  process.exit(1);
}

await mkdir(path.dirname(reportPath), { recursive: true });
await writeFile(reportPath, toMarkdown(result.structured_output as Report, result));
console.log(`Wrote ${path.relative(repoRoot, reportPath)} (${(result.structured_output as Report).findings.length} findings)`);

Dosyaya yazan Claude değil, script. Claude yalnız okuyor ve iki komutu çalıştırıyor, sonucu outputFormat'taki JSON Schema'ya uyan bir nesne olarak döndürüyor. Şema doğrulamasından geçen sonuç structured_output alanında geliyor; alan yoksa script rapor yazmadan hata koduyla çıkıyor. Raporun sonuna reddedilen tool çağrılarını da ekliyoruz: permission_denials, Claude'un izin listesi dışında neyi denediğini gösteriyor.

Seçenekler: İzinler, Ayar Kaynakları ve Sınırlar

settingSources: [] tahmindeki ikinci sorunun cevabı. Belgelere göre bu seçeneği hiç vermemek ["user", "project", "local"] demek: SDK, cwd'deki .claude/settings.json'ı, hook'ları, CLAUDE.md'yi, skill'leri ve kişisel ~/.claude ayarlarını yüklüyor. Bookstore'da bu, on birinci yazıdaki plugin'in de devreye girmesi demek. Boş dizi bunların hepsini kapatıyor, koşunun ihtiyaç duyduğu her şeyi seçeneklerle açıkça veriyoruz. Repo'daki deny kurallarını bu yüzden disallowedTools'ta tekrar yazdık.

Üç tool listesi farklı işler yapıyor. tools, Claude'un hangi built-in tool'ları görebileceğini belirliyor; listede olmayan Edit, Write ya da WebFetch context'e hiç girmiyor. allowedTools listedeki çağrıları sorusuz onaylıyor ama diğerlerini engellemiyor. disallowedTools eşleşen çağrıyı her modda reddediyor. Karar permissionMode: "dontAsk" ile kapanıyor: onay gerektirecek her çağrı soru sorulmadan reddediliyor. Çalışma klasöründeki dosyaları okumak varsayılan olarak onay istemediği için Read, Grep ve Glob çalışıyor. Belgelere göre v2.1.302'den beri SDK, mod verilmezse default modda, yani Manual başlıyor; kimse cevap veremeyeceği için gece koşusunda modu açıkça yazmak daha net.

maxTurns ve maxBudgetUsd iki sınır. Tahmindeki üçüncü sorunun cevabı belgelerde: tek seferlik query()'de sınıra takılan koşu önce error_max_turns ya da error_max_budget_usd alt türünde bir result mesajı veriyor, ardından hata fırlatıyor. Döngüyü try içine almazsan script result'ı işleyemeden düşüyor. Bütçe istemci tarafındaki bir tahmin ve biraz aşılabiliyor; pay bırak. maxBudgetUsd: 0 geçersiz, maxTurns: 0 ise sınırsız demek.

env ilk sorunun cevabı. TypeScript SDK'da env verdiğinde alt sürecin ortamı birleşmiyor, tamamen değişiyor; PATH gidince dotnet da bulunamıyor. Ortam değişkeni eklemen gerekirse { ...process.env, X: "..." } yaz; bizim script'imiz env vermiyor, alt süreç ortamı olduğu gibi devralıyor. Python SDK'da ise değerler devralınan ortamın üzerine birleştiriliyor, iki dil burada farklı.

persistSession: false session dosyasını diske yazmıyor; gece koşusunu sonra resume etmeyeceğimiz için gerekmiyor. model: "sonnet" alias'ı Anthropic API'de en yeni Sonnet'e çözülüyor. Script'i Claude Code binary'si yerine argümanları kaydeden bir stub ile çalıştırdığımızda SDK'nın bu seçenekleri --permission-mode=dontAsk, --setting-sources=, --tools=..., --max-budget-usd=1 ve --no-session-persistence gibi CLI bayraklarına çevirdiğini gördük; onuncu yazıdaki claude -p ile aynı motor.

Gece Çalıştırmak: GitHub Actions

Raporu iş günleri sabah 03.00'te (UTC) bir workflow çalıştırıyor:

name: Dependency report

on:
  schedule:
    - cron: "0 3 * * 1-5"
  workflow_dispatch:

permissions:
  contents: read

concurrency:
  group: dependency-report
  cancel-in-progress: false

jobs:
  report:
    runs-on: ubuntu-latest
    timeout-minutes: 20
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-dotnet@v6
        with:
          dotnet-version: 10.0.x
      - uses: actions/setup-node@v7
        with:
          node-version: 24
      - run: dotnet restore Bookstore.slnx
      - run: npm ci
        working-directory: tools/dependency-report
      - run: npm run report
        working-directory: tools/dependency-report
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
      - uses: actions/upload-artifact@v7
        if: always()
        with:
          name: dependency-report
          path: artifacts/dependency-report.md
          if-no-files-found: ignore

dotnet list package --vulnerable restore edilmiş proje bilgisine ihtiyaç duyduğu için önce dotnet restore var; npm audit yalnız package-lock.json'ı okuyor. Job'ın yetkisi yalnız contents: read; rapor dosyası artifact olarak yükleniyor. API key onuncu yazıdaki gibi bir GitHub secret'ı. Aynı script'i Windows'ta Görev Zamanlayıcı'dan da çalıştırabilirsin; tools ve allowedTools'taki PowerShell satırları Git Bash olmayan bir Windows makinesi için.

Üretimde Dikkat

Her query() bir alt süreç. Belgelerin önerisi SDK'yı bir container ya da benzeri bir izolasyon içinde çalıştırmak: dosya sistemine yalnız gereken klasörleri, mümkünse salt okunur bağlamak, ağı bir proxy üzerinden belirli adreslerle sınırlamak, kimlik bilgilerini agent'ın göremeyeceği bir proxy'den eklemek. GitHub Actions runner'ı her koşuda temiz bir makine olduğu için bizim senaryomuzda bu izolasyonun bir kısmını zaten sağlıyor.

npm audit çıktısı ve paketlerin açıklamaları Claude için veri; içlerinde talimat gibi görünen bir metin olabilir. Yazma tool'larını hiç vermemek ve komutları dar kurallarla sınırlamak bu riski küçültüyor. Konunun derin anlatımı LLM Uygulama Güvenliği serisinde.

settingSources her şeyi kapatmıyor. Makinedeki managed settings, ~/.claude.json ve auto memory her durumda okunuyor; belgeler, birden çok müşteriye hizmet eden bir kurulumda her müşteri için ayrı dosya sistemi, settingSources: [] ve CLAUDE_CODE_DISABLE_AUTO_MEMORY=1 öneriyor.

total_cost_usd liste fiyatıyla hesaplanmış bir tahmin, fatura değil; subagent harcamalarını da içeriyor. Gerçek tüketimi Console'daki raporlardan izle.

Sık Düşülen Tuzaklar

bypassPermissions ile sunucuda çalıştırmak. Gece koşusu kimsenin izlemediği bir süreç; geniş yetki, yanlış giden bir koşunun etkisini büyütüyor.

Bütçe ve tur sınırı koymamak. Takılan bir koşu sabaha kadar token harcayabilir; ikisini timeout-minutes ile birlikte kullan.

Built-in tool'lar yeterken MCP server eklemek. Her server'ın tool adları ve talimatları context'e giriyor, süreci de her koşuda başlıyor; dosya okuyup iki komut çalıştıran bir iş için gerek yok.

allowedTools'u kısıtlama sanmak. Listede olmayan tool'lar kullanılabilir kalıyor; görünmesini istemediğin tool'ları tools ile kaldır.

Küçük Bir Deneme

★☆☆ settingSources satırını silip koşuyu tekrarla ve subtype'ı init olan system mesajındaki plugins, skills ve mcp_servers alanlarını yazdır. İki koşuyu karşılaştır.

★★☆ maxTurns'ü 2'ye indir, try bloğunu kaldır ve script'in nasıl düştüğünü gör. Sonra bloğu geri koyup result'taki subtype'ı raporun başına yazdır.

★★★ permissionMode'u default yap ve bir canUseTool callback'i ekle: onay isteyen her çağrıyı reddetsin ve komutu bir listeye yazsın. dontAsk modunda bu callback'in neden hiç çağrılmadığını belgelerdeki değerlendirme sırasıyla açıkla.

Agent SDK, Claude Code'u kendi kodumuzdan çağırmanın yolunu açtı; bununla birlikte her koşunun bir maliyeti de var. Serinin son yazısında Claude Code'un maliyetini, usage limit'lerini ve güvenli kullanımını bir kontrol listesinde toplayacağız.