Mert Özen Her Satırda Daha İleri
Yapay Zeka Angular ile AI Arayüzleri — #09

Tarayıcıda AI ve Angular 22'nin Deneysel AI Araçları: Built-in AI API'leri ve WebMCP

Mert Özen 14 Kas 2026 15 dk 1 görüntülenme
Tarayıcıda AI ve Angular 22'nin Deneysel AI Araçları: Built-in AI API'leri ve WebMCP

Chrome'un built-in AI API'leri ve Angular 22'nin deneysel WebMCP fonksiyonları, yalnız özellik kontrolü, süre sınırı ve her zaman çalışan bir sunucu yoluyla kullanılmalı. Bu yazıda Chromium 158'in dört ayarında Summarizer'ın downloadable, unavailable ya da cevapsız döndüğünü, yorum özetini önce cihazda sonra sunucuda ürettiğimizi, WebMCP tool'unda await sonrası inject()'in NG0203 verdiğini ve Angular'ın desteklenmeyen tarayıcıda sessiz kaldığını gösteriyoruz.

Sürüm notu: Bu yazıdaki kodlar Angular 22.2 (@angular/core 22.2.6, Angular CLI 22.2.7, TypeScript 6.0, Node.js 24) ve .NET 10 ile derlenip denenmiştir; backend'de Microsoft.Extensions.AI 11.0.0 ve OllamaSharp 5.5.2, tarayıcı denemelerinde Playwright 1.65 ile Chromium 158 (headless shell ve yeni headless mod), sunucu tarafı özette Ollama 0.34.4 (qwen3:4b) kullanılmıştır. Chrome'un cihaz üstü modeli (Gemini Nano) indirilmedi; cihaz yolu sahte bir Summarizer ile denendi. npx skills add çalıştırılmadı.

Önce Tahmin Et

Chrome'un Summarizer API'si Chrome 138'den beri web sayfaları için stable. Bookstore (sitedeki serilerin ortak örnek uygulaması Bookstore, bir online kitapçı) kitap sayfasında yorumların özetini çıkarmak için bunu kullanmak istiyorsun: sunucuya ve modele para ödemeden, kullanıcının cihazında. Playwright'ın Chromium 158'inde Summarizer.availability() ne döner: available mı, downloadable mı, unavailable mı? İkinci soru: Angular 22'nin deneysel WebMCP API'siyle uygulamanın sepetini tarayıcıdaki bir agent'a açtın, ama tarayıcı WebMCP'yi desteklemiyor. Uygulama hata mı verir, uyarı mı yazar, yoksa hiçbir şey olmaz mı?

Bu serinin son yazısı en hızlı eskiyecek olanı: tarayıcının kendi modelleri ve agent'ların web sayfalarını tool olarak kullanması henüz yerleşmiş değil. O yüzden önce ölçüyor, sonra her özelliği desteklemeyen tarayıcıda da çalışacak şekilde (progressive enhancement; temel özellik her yerde çalışır, gelişmiş olan yalnız destekleyen tarayıcıda devreye girer) kuruyoruz.

Tarayıcıda Çalışan Modeller: Built-in AI API'leri

Chrome'un built-in AI API'leri, tarayıcının indirdiği küçük bir modeli (çoğu için Gemini Nano) JavaScript'ten çağırıyor. Chrome dokümanındaki durum tablosu yazım gününde şöyle:

APIWeb sayfalarıNot
Translator, Language DetectorChrome 138'den beri stableAşağıdaki Gemini Nano donanım şartları bunlar için listelenmiyor; mobilde bunlar da yok
SummarizerChrome 138'den beri stableGemini Nano
Prompt API (LanguageModel)Tabloda Chrome 148'den beri stableAynı sayfanın metni hâlâ yalnız extension'lardan söz ediyor; sampling parametreleri ayrı bir origin trial'da
Writer, Rewriter, ProofreaderDeveloper trialBayrakla deneniyor; Proofreader için ayrıca bir origin trial var

Gemini Nano kullanan API'ler için donanım şartları da dokümanda: Windows 10/11, macOS 13 ve üzeri, Linux ya da Chromebook Plus; Chrome profilinin olduğu diskte en az 22 GB boş alan; 4 GB'tan fazla VRAM'li (ekran kartının kendi belleği) bir GPU (ekran kartı işlemcisi) ya da 16 GB RAM ve en az 4 çekirdekli bir CPU; ilk indirme için kotasız bir bağlantı. Android ve iOS'ta bu API'ler yok. Yani Bookstore'un mobil kullanıcılarının tamamı ve masaüstü kullanıcılarının bir kısmı için cevap baştan "unavailable".

Her API'nin availability() metodu dört değerden birini döner: unavailable, downloadable, downloading, available. Model henüz inmemişse create() çağrısı bir kullanıcı etkileşimi (user activation; tıklama ya da tuş basma gibi) gerektiriyor: sayfa kendi kendine gigabaytlarca indirme başlatamıyor.

Önce Ölç: Aynı Sayfa, Dört Tarayıcı Ayarı

Aynı Bookstore sayfasını Playwright'ın iki Chromium modunda ve iki farklı bayrakla açıp her API'nin availability() değerini ve WebMCP nesnesini okudum. İlk denemede script hiç bitmedi; Translator'ın availability() çağrısı headless shell'de hiç dönmüyordu. Her çağrıya 5 saniyelik bir sınır koyunca:

$ node run09-probe.mjs
headless shell, default                          {"ua":"Chrome/158.0.8127.18","Summarizer":"downloadable","Translator":"no answer in 5 s","LanguageDetector":"unavailable","LanguageModel":"downloadable","Writer":"missing","navigatorModelContext":"undefined","documentModelContext":"undefined"}
chromium (new headless), default                 {"ua":"Chrome/158.0.0.0","Summarizer":"unavailable","Translator":"downloadable","LanguageDetector":"unavailable","LanguageModel":"unavailable","Writer":"missing","navigatorModelContext":"undefined","documentModelContext":"undefined"}
chromium, --enable-features=WebMCP               {"ua":"Chrome/158.0.0.0","Summarizer":"unavailable","Translator":"downloadable","LanguageDetector":"unavailable","LanguageModel":"unavailable","Writer":"missing","navigatorModelContext":"undefined","documentModelContext":"object"}
chromium, experimental web platform features     {"ua":"Chrome/158.0.0.0","Summarizer":"unavailable","Translator":"downloadable","LanguageDetector":"unavailable","LanguageModel":"unavailable","Writer":"unavailable","navigatorModelContext":"undefined","documentModelContext":"object"}

Aynı sürüm, aynı makine, dört farklı sonuç. Yeni headless modda konsola şu uyarı da düştü: "On-device AI is disabled in Chrome settings. To use Built-In AI APIs, enable "On-device AI" in chrome://settings/ai." Yani API'nin varlığı yetmiyor; kullanıcı ya da şirket politikası cihaz üstü AI'ı ayarlardan kapatabiliyor. Bundan çıkan üç kural kodun iskeleti oldu: API'nin varlığını kontrol et, availability()'yi bir süre sınırıyla bekle ve her durumda çalışan bir sunucu yolun olsun.

Bookstore'da Yorum Özeti: Önce Cihaz, Sonra Sunucu

Özellik kontrolü ve cihazda özet bir dosyada. Summarizer TypeScript'in DOM (Document Object Model; tarayıcının sayfa API'leri) tiplerinde olmadığı için kullandığımız kadarını tanımladım:

// Özellik kontrolü: API yoksa ya da availability() 3 saniyede dönmezse cihazda özet yok sayılır
export async function onDeviceAvailability(): Promise<Availability> {
  const summarizer = api();
  if (!summarizer) {
    return 'unavailable';
  }
  const timeout = new Promise<Availability>((resolve) => setTimeout(() => resolve('unavailable'), 3000));
  return Promise.race([summarizer.availability(OPTIONS), timeout]).catch(() => 'unavailable' as const);
}

// Model gerekiyorsa indirir (kullanıcı etkileşimi şart), ilerlemeyi 0..1 olarak bildirir ve özetler
export async function summarizeOnDevice(text: string, onProgress: (loaded: number) => void, signal?: AbortSignal): Promise<string> {
  const summarizer = api();
  if (!summarizer) {
    throw new Error('Summarizer API is not available.');
  }
  const session = await summarizer.create({
    ...OPTIONS,
    monitor: (m) => m.addEventListener('downloadprogress', (e) => onProgress((e as ProgressEvent).loaded)),
  });
  try {
    return await session.summarize(text, { signal });
  } finally {
    session.destroy();
  }
}

OPTIONS özetin tipini (tldr), uzunluğunu (short), biçimini (plain-text) ve bağlamını ("Customer reviews of a book in an online bookstore.") veriyor. Component bu iki fonksiyonu Angular'ın resource ailesiyle bağlıyor: yorumlar httpResource ile, cihaz durumu ve özet resource ile geliyor. İkisi de Angular 22.0'da public API oldu; 2. yazıdaki resource stream'i de bu ailenin parçası.

protected readonly reviews = httpResource<Review[]>(() => `/reviews/${this.isbn()}`);
protected readonly availability = resource({ loader: () => onDeviceAvailability() });
protected readonly progress = signal<number | null>(null);
private readonly preferDevice = signal(false);

protected readonly summary = resource({
  params: () => {
    const text = this.reviewText();
    const availability = this.availability.value();
    return text === undefined || availability === undefined
      ? undefined
      : { isbn: this.isbn(), text, onDevice: availability === 'available' || this.preferDevice() };
  },
  loader: async ({ params, abortSignal }): Promise<Summary> => {
    if (params.onDevice) {
      const text = await summarizeOnDevice(params.text, (p) => this.progress.set(p), abortSignal);
      this.progress.set(null);
      return { summary: text, source: 'device' };
    }
    const response = await fetch(`/reviews/${params.isbn}/summary`, { signal: abortSignal });
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }
    return response.json();
  },
});

params undefined döndükçe loader çalışmıyor; yorumlar ve cihaz durumu gelince tek bir karar veriliyor. Sunucu tarafı, serinin başından beri kullandığımız IChatClient'la özet üretiyor ve sonucu 10 dakika cache'liyor; aynı kitabın özeti her ziyaretçi için yeniden üretilmiyor:

// Cihazda özetleyemeyen tarayıcılar için sunucu özeti; yorumlar sık değişmediği için cache'lenir
app.MapGet("/reviews/{isbn}/summary", async Task<IResult> (string isbn, IChatClient chat, IMemoryCache cache,
    ILogger<ReviewSummary> logger, CancellationToken ct) =>
{
    if (!ByIsbn.TryGetValue(isbn, out IReadOnlyList<Review>? reviews))
    {
        return TypedResults.NotFound();
    }
    ReviewSummary? summary = await cache.GetOrCreateAsync($"review-summary:{isbn}", async entry =>
    {
        entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10);
        string text = string.Join('\n', reviews.Select(r => $"- ({r.Rating}/5) {r.Text}"));
        ChatResponse response = await chat.GetResponseAsync(
            [
                new(ChatRole.System, "Summarize customer reviews of a book in two short sentences. " +
                    "Mention what readers liked and what they criticised. Do not invent details."),
                new(ChatRole.User, text),
            ], cancellationToken: ct);
        logger.LogInformation("Review summary for {Isbn}: {Tokens} output tokens", isbn, response.Usage?.OutputTokenCount);
        return new ReviewSummary(response.Text.Trim(), "server");
    });
    return TypedResults.Ok(summary);
}).RequireRateLimiting("chat");
$ time curl -s localhost:19640/reviews/9780441172719/summary
{"summary":"Readers praised the book's incredible world building, Paul's story feeling modern, and its great ideas.
They criticized the slow start with unfamiliar terms in the first hundred pages and considered the appendix and
glossary almost required reading.","source":"server"}
curl -s localhost:19640/reviews/9780441172719/summary  0.00s user 0.00s system 0% cpu 9.783 total

$ time curl -s localhost:19640/reviews/9780441172719/summary
curl -s localhost:19640/reviews/9780441172719/summary  0.00s user 0.00s system 64% cpu 0.006 total

# sunucu log'u
Review summary for 9780441172719: 389 output tokens

İlk istek 9,8 saniye ve 389 output token, ikincisi cache'ten 6 milisaniye. Dune sayfasını iki Chromium modunda açtım:

$ node run09.mjs shell
   163 ms Summarizing reviews…
   267 ms Readers praised the book's incredible world building, ... | Summarized by the Bookstore server.
          AI-generated, may contain mistakes. | Summarize on this device (one-time model download)

$ node run09.mjs chromium
   160 ms Summarizing reviews…
[console.warning] On-device AI is disabled in Chrome settings. To use Built-In AI APIs, enable "On-device AI" in chrome://settings/ai.
   263 ms Readers praised the book's incredible world building, ... | Summarized by the Bookstore server.
          AI-generated, may contain mistakes.

Headless shell'de model downloadable olduğu için kullanıcıya sunucu özeti gösteriliyor ve yanında bir buton çıkıyor; yeni headless modda cihaz yolu kapalı, buton yok. İki durumda da kullanıcı özeti 300 milisaniyenin altında görüyor. Kaynağı ("Summarized by the Bookstore server.") ve 7. yazıdaki "AI-generated" etiketini her durumda yazıyorum: cihazda üretilen bir özet ile sunucuda üretilen arasında kalite farkı olabilir ve kullanıcı neyi okuduğunu bilmeli.

Model İndirme ve Kullanıcı Etkileşimi

Buton, preferDevice signal'ını true yapıyor; summary resource'u yeniden çalışıyor ve create() tıklamanın sağladığı kullanıcı etkileşimiyle indirmeyi başlatıyor. Gerçek modeli bu yazı için indirmedim. Akışı denemek için Playwright'ta sayfaya, availability()'si downloadable dönen ve create() sırasında üç downloadprogress event'i gönderen sahte bir Summarizer enjekte ettim:

# SAHTE Summarizer: model indirilmedi
$ node run09.mjs fake-download
    212 ms Readers praised ... | Summarized by the Bookstore server. ... | Summarize on this device (one-time model download)
    340 ms Summarizing 4 reviews… [progress 0]
    569 ms Summarizing 4 reviews… [progress 0.5]
    882 ms Summarizing 4 reviews… [progress 1]
   1193 ms FAKE on-device summary of 4 reviews. | Summarized on your device. AI-generated, may contain mistakes.

Bu çıktı bir şeyi daha gösteriyor: buton tıklanınca sunucu özeti ekrandan kayboluyor ve indirme bitene kadar "Summarizing…" yazıyor. resource'un params'ı değiştiğinde değer sıfırlanıyor. Gerçek bir indirme dakikalar sürebileceği için bu kötü bir deneyim; sunucu özeti indirme bitene kadar ekranda kalmalı (Küçük Bir Deneme'de).

Cihazda özetin değeri maliyet ve gecikme: model sunucuda çalışmıyor, istek sayısı ve token faturası azalıyor. Ama özet kalitesi cihazdaki modele bağlı ve kullanıcıların yalnız bir kısmında var. Bu yüzden sunucu yolu ana yol, cihaz yolu bir iyileştirme olarak kalmalı.

WebMCP: Uygulamayı Tarayıcıdaki Agent'a Açmak

WebMCP, bir web sayfasının kendi fonksiyonlarını tarayıcıdaki bir agent'a JSON Schema ile tanımlanmış tool'lar olarak açmasını öneren bir standart taslağı; W3C'de (World Wide Web Consortium; web standartlarını yayımlayan kuruluş) bir community group taslağı. Chrome dokümanına göre Chrome 149'dan itibaren bir origin trial olarak, yerel geliştirmede de chrome://flags/#enable-webmcp-testing bayrağıyla açılıyor; blink-dev'deki deneme onayı Chrome'un 149 ile 156 numaralı sürümlerini (M149–M156) kapsıyordu. Yukarıdaki ölçümde Chromium 158'de nesne varsayılan olarak yok, --enable-features=WebMCP ile document.modelContext olarak geliyor; navigator.modelContext ise yok.

Angular 22'nin @angular/core'unda iki deneysel fonksiyon var: bir component içinde, onun ömrüne bağlı tool tanımlayan declareExperimentalWebMcpTool ve uygulama ya da route provider'larında tool listesi veren provideExperimentalWebMcpTools. Ninja Squad'ın 22.0 yazısında bu ikincisi tekil (provideExperimentalWebMcpTool) geçiyor; 22.2.6'nın type tanımlarında adı çoğul. Deneysel bir API'de neyin doğru olduğunu ancak kurduğun paketin kendisi söylüyor. Bookstore'un tool'ları:

// Tarayıcıdaki bir agent'a açılan tool'lar; execute Angular'ın injection context'inde çalışır
export const BOOKSTORE_WEBMCP_TOOLS: WebMcpToolDescriptor[] = [
  {
    name: 'search_catalog',
    description: 'Searches the Bookstore catalog by title, author or genre and returns matching books with prices in TRY.',
    inputSchema: {
      type: 'object',
      properties: { query: { type: 'string', description: 'Title, author or genre, for example "crime".' } },
      required: ['query'],
    },
    annotations: { readOnlyHint: true },
    execute: async (args) => {
      const query = String((args as { query?: unknown }).query ?? '');
      const books = await firstValueFrom(inject(HttpClient).get<BookCardData[]>('/catalog/search', { params: { q: query } }));
      return JSON.stringify(books);
    },
  },
  {
    name: 'add_to_cart',
    description: 'Adds one book to the customer\'s cart by ISBN-13. It does not place an order; the customer checks out.',
    inputSchema: {
      type: 'object',
      properties: { isbn: { type: 'string', pattern: '^\\d{13}$' } },
      required: ['isbn'],
    },
    execute: async (args) => {
      // inject() yalnız ilk await'ten önce çalışır; injection context senkron kısımla sınırlı
      const http = inject(HttpClient);
      const cart = inject(Cart);
      const isbn = String((args as { isbn?: unknown }).isbn ?? '');
      // Agent'ın gönderdiği ISBN'e güvenmiyoruz: kitap katalogdan, fiyat sunucudan gelir
      const [book] = await firstValueFrom(http.get<BookCardData[]>('/catalog/search', { params: { isbn } }));
      if (!book) {
        return `No book with ISBN ${isbn}.`;
      }
      cart.add(book);
      return `Added "${book.title}" to the cart. The cart has ${cart.count()} item(s).`;
    },
  },
];
providers: [
  provideBrowserGlobalErrorListeners(),
  provideRouter(routes, withComponentInputBinding()),
  provideHttpClient(withFetch()),
  // Deneysel: tarayıcı WebMCP desteklemiyorsa Angular hiçbir şey kaydetmez
  provideExperimentalWebMcpTools(BOOKSTORE_WEBMCP_TOOLS),
]

add_to_cart'ın ilk sürümünde inject(Cart) satırı await'ten sonraydı. Tool'u bir agent gibi document.modelContext.executeTool ile çağırınca:

[console.error] WebMCP tool execution failed: Uncaught Error: NG0203: The `_Cart` token injection failed. `inject()`
function must be called from an injection context such as a constructor, a factory function, a fiel...
{ "error": "Error: page.evaluate: OperationError: Tool execution failed." }
header: Cart: 0

Angular'ın kaynağına baktım: execute'u runInInjectionContext içinde çağırıyor, ama bu context yalnız fonksiyonun senkron kısmında geçerli; ilk await'ten sonra kaybolan sıradan bir JavaScript çağrısı. Bağımlılıkları en başta almak sorunu çözdü. Düzeltmeden sonra tool listesi ve iki çağrı:

$ node run09.mjs webmcp
{
  "tools": [
    "add_to_cart {}",
    "search_catalog {\"consequentialHint\":false,\"debugging\":false,\"readOnlyHint\":true,\"untrustedContentHint\":false}"
  ],
  "search": "[{\"isbn\":\"9780062073488\",\"title\":\"And Then There Were None\",\"author\":\"Agatha Christie\",\"priceTry\":16.9},...",
  "add": "Added \"The Hound of the Baskervilles\" to the cart. The cart has 1 item(s).",
  "bad": "No book with ISBN 9780000000000."
}
header: Cart: 1

$ node run09.mjs no-webmcp
{ "modelContext": "undefined" }
header: Cart: 0

Önce Tahmin Et'in ikinci sorusunun cevabı: hiçbir şey olmaz. Angular'ın kaynağında declareExperimentalWebMcpTool, document.modelContext ya da navigator.modelContext bulamazsa ya da sunucu tarafı render sırasında çağrılırsa sessizce dönüyor; konsolda tek satır yok. Bu, progressive enhancement için doğru davranış, ama hata ayıklarken "tool'um neden görünmüyor" sorusunun cevabını da saklıyor. Bir bedeli de var: provideHttpClient ve tool tanımları ilk açılış bundle'ına girdi ve main 263,8 kB'tan 287,4 kB'a çıktı; WebMCP olmayan tarayıcıda da bu kod iniyor.

Signal Forms ve Güvenlik Soruları

@angular/forms/signals'taki provideExperimentalWebMcpForms() bir adım daha ileri gidiyor: form()'a verilen experimentalWebMcpTool seçeneği (name, description ve annotations.consequentialHint) bir Signal Form'u tool'a çeviriyor. 22.0 duyurusuna göre input şeması formun alanlarından üretiliyor ve tool çağrıldığında form doldurulup gönderiliyor. Bunu Bookstore'da kurmadım; aşağıdaki sorular cevaplanmadan bir sipariş ya da ödeme formunu agent'a açmak istemezdim.

Bir WebMCP tool'u, kullanıcının oturumuyla çalışan yeni bir API endpoint'i. 5. yazıdaki kurallar burada da geçerli: agent'tan gelen veri doğrulanır (ISBN deseni, katalogdan gelen fiyat), geri alınamayan işlem tool olmaz ya da onay ister. Tarayıcı tarafında şunlar var: tool tanımındaki readOnlyHint, consequentialHint ve untrustedContentHint ipuçları; Chrome dokümanına göre hassas işlemlerde bir onay penceresi ve varsayılanı self olan tools Permissions Policy'si, yani cross-origin bir iframe allow="tools" olmadan tool açamıyor. Kendi tarafında da sorman gerekenler: tool'u hangi agent çağırıyor (bir tarayıcı extension'ı da çağırabiliyor), sonucunda kullanıcıya ait hangi veri dışarı çıkıyor ve agent'ın okuduğu sayfadaki bir prompt injection seni bu tool'u çağırmaya yönlendirebilir mi? Tehdit modelini LLM Uygulama Güvenliği serisi ele alıyor.

Angular'ın AI Geliştirme Araçları

Angular'ın AI'a dokunan diğer tarafı uygulamanın değil, geliştiricinin araçları. Angular CLI 20.1'den beri bir MCP (Model Context Protocol; modellerin dış araçlara ve verilere bağlanması için açık protokol) server'ı içeriyor ve IDE'deki bir AI asistanı ona bağlanabiliyor. 22.2.7'de bir MCP istemcisi gibi tools/list çağırdım:

$ npx ng mcp --help
ng mcp

Options:
  --help        Shows a help message for this command in the console.  [boolean]
  --root        Allowed root directory paths for filesystem access and workspace discovery. Can be specified multiple times.  [array]
  --read-only   Only register read-only tools.  [boolean] [default: false]
  --local-only  Only register tools that do not require internet access.  [boolean] [default: false]

$ node mcp-list.mjs
server: angular-cli-server 22.2.7
ai_tutor                     readOnly=true openWorld=false
get_best_practices           readOnly=true openWorld=false
search_documentation         readOnly=true openWorld=true
list_projects                readOnly=true openWorld=false
onpush_zoneless_migration    readOnly=true openWorld=false
run_target                   readOnly=false openWorld=false
devserver_start              readOnly=false openWorld=false
devserver_stop               readOnly=false openWorld=false
devserver_wait_for_build     readOnly=true openWorld=false

$ node mcp-list.mjs --read-only --local-only
server: angular-cli-server 22.2.7
ai_tutor                     readOnly=true openWorld=false
get_best_practices           readOnly=true openWorld=false
list_projects                readOnly=true openWorld=false
onpush_zoneless_migration    readOnly=true openWorld=false
devserver_wait_for_build     readOnly=true openWorld=false

--read-only --local-only ile asistan build ve dev server çalıştıramıyor, internete de çıkamıyor; ekipte varsayılanı bu yapmak mantıklı. Ninja Squad'ın 22.0 yazısına göre kod üretimi rehberliği artık ayrı bir "skill" paketinde (npx skills add https://github.com/angular/skills; angular-new-app ve angular-developer) ve bu yüzden 22'de MCP server'dan find_examples ile modernize tool'ları kaldırıldı. Skill paketini bu yazıda kurmadım.

Uygulama tarafında bu seride kullandığımız API'lerin durumu 22.2.6'nın type tanımlarında net: resource ve httpResource @publicApi 22.0, yani stable; 22.0'da gelen debounced() (bir signal'ı geciktirip loading durumu olan bir Resource döndüren yardımcı) ve üç WebMCP fonksiyonu @experimental. Deneysel olanlar bir sonraki minor sürümde değişebilir.

Üretimde Dikkat

Cihaz üstü AI'ı ölçerek aç. Kullanıcılarının ne kadarında available, ne kadarında downloadable, ne kadarında unavailable döndüğünü topla. Bu yazıdaki dört tarayıcı ayarından yalnız birinde Summarizer modeli indirilebilir durumdaydı; gerçek kullanıcılarda oran senin kitlene bağlı.

availability()'ye süre sınırı koy. Ölçümde bir çağrı hiç dönmedi; sınır olmasa sayfa özeti sonsuza kadar bekleyecekti.

İndirmeyi kullanıcıya bırak ve boyutunu söyle. Tarayıcı zaten kullanıcı etkileşimi istiyor; butonun metni bir indirme olacağını açıkça yazmalı. Ölçülü (kotalı) bağlantıdaki kullanıcıya bu butonu göstermemek daha iyi.

WebMCP'yi şimdilik bir deney olarak tut. Yazılardaki API adı ile paketteki ad bile uyuşmuyor, tarayıcı tarafı origin trial ile bayrak arasında. Production'da açacaksan yalnız okuma yapan tool'larla başla, her çağrıyı sunucu tarafında log'la ve agent'ın kullanıcı adına yaptığı her işlemi kullanıcının göreceği bir yerde göster.

Sık Düşülen Tuzaklar

Birincisi, 'Summarizer' in self kontrolünü yeterli saymak. API vardı ama unavailable döndü, ayarlarda kapalıydı ya da hiç cevap vermedi.

İkincisi, sunucu yolunu sonraya bırakmak. Mobil kullanıcıların tamamı ve masaüstünün bir kısmı için tek yol sunucu.

Üçüncüsü, WebMCP tool'unun execute'unda await'ten sonra inject() çağırmak. NG0203 (Angular'ın injection context dışındaki inject() hatası) ile tool çalışmadı ve agent'a yalnız "Tool execution failed." gitti.

Dördüncüsü, Angular'ın sessiz kalmasını "çalışıyor" sanmak. Tarayıcı WebMCP desteklemiyorsa ya da bayrak kapalıysa hiçbir uyarı yok; tool'ların kayıtlı olduğunu document.modelContext.getTools() ile kontrol et.

Beşincisi, geri alınamayan bir işlemi tool olarak açmak. add_to_cart geri alınabilir ve siparişi kullanıcı veriyor; ödeme ya da sipariş tool'u açacaksan consequentialHint ve kullanıcı onayı şart.

Küçük Bir Deneme

★☆☆ İndirme sürerken sunucu özetini ekranda tut: cihaz özetini ayrı bir resource'a al ya da linkedSignal ile son başarılı özeti sakla, indirme bitince değiştir.

★★☆ Translator API'siyle yorumları kullanıcının diline çevir: Translator.availability({ sourceLanguage: 'en', targetLanguage: 'tr' }) için de süre sınırı koy ve desteklenmeyen tarayıcıda çeviriyi sunucuya bırak. Language Detector ile yorumun dilini önce tespit et.

★★★ Bookstore'un arama formunu Signal Forms ile yeniden yaz, experimentalWebMcpTool seçeneğiyle tool'a çevir ve provideExperimentalWebMcpForms() ekle. getTools() ile üretilen input şemasına bak: elle yazdığın search_catalog şemasından farkı ne?

Dokuz yazı boyunca Bookstore asistanının Angular tarafını kurduk: SSE (Server-Sent Events; sunucudan tarayıcıya tek yönlü event akışı) akışını signal'larla göstermek, state machine ile hata ve iptal, güvenli markdown, tool çağrıları, onay ve generative UI, standart protokoller, erişilebilirlik, testler ve son olarak tarayıcıdaki modeller. Hepsinin ortak noktası aynı: model her seferinde farklı cevap veriyor, arayüz her seferinde aynı şekilde doğru davranmalı. Backend tarafının ayrıntıları .NET ile Yapay Zeka ve AI Agent'ları ve MCP serilerinde, güvenlik tarafı LLM Uygulama Güvenliği serisinde.