Sayfa Araçları (Beta) — Ajan Sitenizde Eylem Yapsın
Siteniz JavaScript fonksiyonlarını araç olarak kaydeder; widget ajanı bunları çağırabilir — ziyaretçinin kendi tarayıcı sekmesinde, mevcut oturumunun içinde.
Şimdiye kadar widget ajanı web siteniz hakkında konuşabiliyordu. Sayfa araçlarıyla artık sitenizde eylem yapabilir: ziyaretçinin sepetine bakmak, bir ürünü vurgulamak, form doldurmak, sipariş sorgulamak — sitenizin kendi JavaScript'ini kullanarak, oturum açmış ziyaretçi olarak. Onun girişi, onun sepeti, onun fiyatları, onun hesabı. OAuth yok, API anahtarı yok, backend entegrasyonu yok: kaydettiğiniz araçlar, iznin tamamıdır. Kaydetmediğiniz bir şeyi ajan yapamaz.
Bu tasarımdan birkaç sonuç çıkar:
- Araçlar tek bir ziyaretçinin sekmesine aittir. Yalnızca o ziyaretçinin widget oturumunda var olurlar ve tam olarak ziyaretçinin tarayıcısının zaten sahip olduğu erişimle çalışırlar. Telefon ve API oturumları onları asla görmez.
- Ajan ekranınızı görmez. Konuşmaya yalnızca bir aracın açıkça döndürdüğü metin girer. Gezinme veya vurgulama ziyaretçinin gördüğünü değiştirir; ajan yalnızca aracın dönüş değerinin söylediğini öğrenir.
- Araç listeleri canlıdır. Widget, araçları oturum açılırken keşfeder ve konuşmanın ortasındaki değişiklikleri de alır — tek sayfa uygulamaları araç setini her rota değişiminde değiştirebilir.
:::info Standartlarla uyumlu
Sayfa araçları, gelişmekte olan WebMCP taslağını (navigator.modelContext) ve @mcp-b polyfill ekosistemini konuşur. Siteniz başka ajanlar için zaten WebMCP araçları kaydediyorsa, BESS widget'ı onları olduğu gibi kullanır — aşağıdaki opt-in dışında BESS'e özel eklenecek bir şey yoktur.
:::
Etkinleştirme (iki anahtar)
Sayfa araçları varsayılan olarak kapalıdır ve iki tarafın da açık onayını gerektirir:
- Kontrol paneli — widget'ı düzenleyin ve Allow page tools (WebMCP) kutusunu işaretleyin.
- Snippet — sayfanızdaki gömme etiketine
data-page-tools="1"ekleyin:
<script src="https://api.bess-ai.com/embed.js"
data-widget-id="bess_pk_live_XXXXXXXXXXXX"
data-page-tools="1"
async></script>
İkisi birden olmadan hiçbir şey değişmez: widget öncekiyle tamamen aynı davranır ve sayfa kayıtları yok sayılır.
Araç kaydetme
Aşağıda, herhangi bir sayfaya yapıştırıp uyarlayabileceğiniz eksiksiz, bağımlılıksız bir kayıt örneği var. İki bölümden oluşur: küçük bir köprü (tel protokolü — bir kez yazılır; ya da yerine @mcp-b/global kullanın) ve araçlarınız (asıl bakımını yapacağınız kısım).
İki araç kaydeder: bir okuma (get_cart_summary, readOnlyHint: true işaretli olduğu için sessizce çalışır) ve bir eylem (add_to_cart, ajan çalıştırmadan önce ziyaretçiden onay almak zorundadır).
<script>
/* ---- 1. Köprü: sayfa içi minimal araç sunucusu (bir kez yazılır) ---------
Widget'ın dinlediği @mcp-b tab tel biçimini konuşur. Zaten @mcp-b/global
veya navigator.modelContext kullanıyorsanız bu bloğu atlayın ve
araçlarınızı orada kaydedin — widget ikisini de anlar. */
(function () {
var CHANNEL = "mcp-default";
var tools = {};
function send(payload) {
window.postMessage(
{ channel: CHANNEL, type: "mcp", direction: "server-to-client", payload: payload },
"*"
);
}
window.addEventListener("message", function (e) {
var d = e.data;
if (e.source !== window || !d || d.channel !== CHANNEL ||
d.type !== "mcp" || d.direction !== "client-to-server") return;
var p = d.payload;
if (p === "mcp-check-ready") { send("mcp-server-ready"); return; }
if (!p || typeof p !== "object") return;
handle(p);
});
function handle(msg) {
function reply(result) { send({ jsonrpc: "2.0", id: msg.id, result: result }); }
function fail(code, message) {
send({ jsonrpc: "2.0", id: msg.id, error: { code: code, message: message } });
}
if (msg.method === "initialize") {
reply({
protocolVersion: (msg.params && msg.params.protocolVersion) || "2025-06-18",
capabilities: { tools: { listChanged: true } },
serverInfo: { name: "my-site", version: "1.0.0" },
});
} else if (msg.method === "tools/list") {
reply({
tools: Object.keys(tools).map(function (name) {
var t = tools[name];
return { name: t.name, description: t.description,
inputSchema: t.inputSchema, annotations: t.annotations };
}),
});
} else if (msg.method === "tools/call") {
var tool = tools[msg.params && msg.params.name];
if (!tool) return fail(-32602, "Unknown tool: " + (msg.params && msg.params.name));
Promise.resolve()
.then(function () { return tool.execute((msg.params && msg.params.arguments) || {}); })
.then(function (text) { reply({ content: [{ type: "text", text: String(text) }] }); })
["catch"](function (err) {
reply({ content: [{ type: "text", text: String(err) }], isError: true });
});
} else if (msg.id !== undefined && msg.method !== "notifications/initialized") {
fail(-32601, "Method not found: " + msg.method);
}
}
// Araç seti her değiştiğinde yeniden çağırın (ör. SPA rota değişimlerinde).
window.registerPageTools = function (list) {
tools = {};
list.forEach(function (t) { tools[t.name] = t; });
send({ jsonrpc: "2.0", method: "notifications/tools/list_changed" });
};
send("mcp-server-ready"); // widget zaten dinliyorsa diye
})();
/* ---- 2. Araçlarınız (bakımını yapacağınız kısım) ------------------------ */
registerPageTools([
{
name: "get_cart_summary",
description: "Ziyaretçinin mevcut sepetini oku: ürün adları, adetler ve toplam.",
inputSchema: { type: "object", properties: {} },
annotations: { readOnlyHint: true }, // salt okunur -> onaysız çalışır
execute: function () {
// Gerçek sepet sorgunuzla değiştirin.
return JSON.stringify({ items: [{ sku: "RUG-114", qty: 1 }], total: "4800 TRY" });
},
},
{
name: "add_to_cart",
description: "SKU ile ziyaretçinin sepetine ürün ekle.",
inputSchema: {
type: "object",
properties: {
sku: { type: "string", description: "Ürün SKU'su, ör. RUG-114" },
quantity: { type: "integer", description: "Kaç adet ekleneceği (varsayılan 1)" },
},
required: ["sku"],
},
// readOnlyHint yok -> ajan önce ziyaretçiden onay almak zorunda.
execute: function (args) {
// Gerçek sepete-ekle çağrınızla değiştirin.
return (args.quantity || 1) + " x " + args.sku + " sepete eklendi.";
},
},
]);
</script>
Sitenizi açın, bir widget konuşması başlatın ve "sepetimde ne var?" gibi bir şey sorun — ajan, araçları oturum başında otomatik olarak keşfeder.
:::note Tek sayfa uygulamaları
İlgili araç seti her değiştiğinde registerPageTools([...]) fonksiyonunu yeniden çağırın — örneğin rota değişimlerinde, böylece add_to_cart aracı yalnızca ürün sayfalarında var olur. Ajanın araç listesi konuşmanın ortasında güncellenir ve oturum başladığında var olmayan bir aracı çağırabilir.
:::
Üretimden gelen tasarım desenleri
Aşağıdakilerin hepsi sayfa araçlarını kendi ürünlerimizde — bess-ai.com ve BESS konsolunun yerleşik yardımcısında (copilot) — çalıştırırken öğrendiklerimiz. Gönül rahatlığıyla kopyalayın.
Yalnızca ekranda olanı kaydedin. Tek bir dev araç seti göndermeyin. Rota (veya sihirbaz adımı) başına bir kayıt tutun ve gezinmede yeniden kaydedin; böylece ödeme aracı yalnızca ödeme sayfasında, form doldurma aracı yalnızca formu görünürken var olur. Ajanın listesi konuşmanın ortasında güncellenir — ve bir LLM göremediği aracı yanlış kullanamaz:
var ORTAK = [getPageContext];
var ROTAYA_GORE = {
"/urun": [addToCart, highlightProduct],
"/odeme": [getCartSummary, applyCoupon],
};
function rotaDegisti(path) {
registerPageTools(ORTAK.concat(ROTAYA_GORE[path] || []));
}
Her zaman bir bağlam aracı ekleyin. Ziyaretçinin nerede olduğunu ve sayfanın durumunu ("ödeme, 2. adım, sepette 3 ürün, kupon alanı görünür") döndüren salt-okunur bir get_page_context() kaydedebileceğiniz en değerli araçtır. Ajan eyleme geçmeden önce onunla yönünü bulur — onsuz kör hareket eder. Özel veri dökümü değil, yapı döndürün.
Formları tipli araçlar olarak aynalayın. Ajanın form doldurmasını istiyorsanız genel bir set_field(ad, deger) sunmayın — her form için, inputSchema'sı formun alanlarını aynalayan (seçimler için enum, zorunlular için required) tek bir araç kaydedin. LLM'ler şemaları doğal okur: ajan eksik değerleri konuşarak toplar, aracı bir kez çağırır ve execute fonksiyonunuz gerçek alanları ziyaretçi izlerken doldurur. Göndermeyi insana bırakın — her şeyi doldurun, hiçbir şeye tıklamayın.
Dönüş değerlerini ajanın kulağı için yazın. Ajanın öğrendiği tek şey dönüş metnidir. "2 × RUG-114 eklendi — sepet toplamı 9600 TL" ziyaretçinin bir sonraki sorusunu yanıtlamayı sağlar; "Tamam" yeni bir araç çağrısına zorlar. Kısa, insan-okur, durum taşıyan.
Eklenti veya ajanssanız kaynağınızı etiketleyin. İsteğe bağlı annotations: { "x-bess-source": "plugin:temam" } widget sahibinin kontrol panelindeki araç tablosunda aracın kaynağı olarak görünür — sahipler hangi aracın sizin entegrasyonunuzdan, hangisinin kendi kodundan geldiğini her zaman bilir.
Yapay zeka kod asistanları için
Kaydı Claude Code, Cursor veya başka bir kod asistanıyla mı yazıyorsunuz? Bu sözleşmeyi bu sayfayla birlikte asistanın bağlamına yapıştırın (BESS MCP sunucusuna bağlı asistanlar read_guide("guides/widget-page-tools") ile canlı da çekebilir):
BESS sayfa-araçları sözleşmesi (v1):
- Taşıma: "mcp-default" kanalında @mcp-b sekme protokolü (kılavuzdaki köprü
bloğu) veya varsa navigator.modelContext. BESS widget'ı ikisini de dener.
- Adlar: ^[a-zA-Z][a-zA-Z0-9_]{0,63}$ ; snake_case; benzersiz (ilk kazanır).
Asla "user_confirmed" adında parametre tanımlamayın (onay kapısına ayrılmıştır).
- Sınırlar: sayfa başına <=32 araç; araç açıklaması <=1024 karakter; şema içi
açıklamalar <=256; inputSchema <=16KB; sonuçlar <=8KB; çalışma <=10sn.
- Onay: annotations.readOnlyHint=true -> sessiz çalışır; geri kalan her şey ->
platformun zorladığı ziyaretçi onayı. Durum değiştiren bir aracı asla
readOnlyHint işaretlemeyin.
- SPA: gezinmede geçerli rotanın araç setini yeniden kaydedin ve
notifications/tools/list_changed yayınlayın. Yalnızca geçerli ekranda anlamlı
araçları kaydedin.
- Her zaman rota + sayfa durumu döndüren salt-okunur bir get_page_context()
aracı ekleyin (veri dökümü değil, yapı).
- Form doldurma: form başına tek tipli araç, şema alanları aynalar; alanları
görünür şekilde doldurun; asla otomatik göndermeyin.
- Dönüşler: ortaya çıkan durumu taşıyan kısa, insan-okur metinler.
- Test: widget ayarı "Allow page tools" AÇIK + gömme koduna data-page-tools="1";
YENİ bir widget oturumu başlatın; keşfedilen araçlar widget'ın panel araç
tablosunda ve tarayıcı konsolundaki [bess-page-tools] satırında görünür.
Kurallar
Araç tanımları sayfanızdan bir yapay zeka konuşmasına geçer; bu yüzden platform onları yapısal olarak doğrular. Bir kuralı bozan araç uyarı kaydıyla birlikte düşürülür — asla sessizce yeniden yazılmaz:
| Gereksinim | Sınır |
|---|---|
| Araç adı | ^[a-zA-Z][a-zA-Z0-9_]{0,63}$ ile eşleşmeli — harfle başlar; harf, rakam, alt çizgi; en fazla 64 karakter. Tire yok, Python ayrılmış sözcükleri yok. Aynı ad iki kez kaydedilirse ilki geçerlidir. |
| Araç açıklaması | en fazla 1024 karakter (kontrol karakterleri temizlenir). |
| Parametre adları | tanımlayıcı (identifier) tarzında (snake_case uygundur). user_confirmed adı onay kapısı için ayrılmıştır — onu bildiren araç düşürülür. |
inputSchema içindeki açıklamalar | her biri en fazla 256 karakter. |
inputSchema boyutu | serileştirilmiş en fazla 16 KB. |
| Sayfa başına araç sayısı | en fazla 32. |
| Araç sonucu | serileştirilmiş en fazla 8 KB — daha büyük sonuçlar açık bir işaretle kırpılır. |
| Çalışma süresi | sayfa yanıt vermezse çağrı ~10 saniyede zaman aşımına uğrar. Araçları hızlı tutun. |
Bir aracın döndürdüğü her şey güvenilmeyen sayfa içeriği olarak ele alınır — ajana bunları talimat olarak değil, veri olarak kullanması söylenir.
Onay: sessiz okumalar, onaylı eylemler
Her sayfa aracı iki kademeden birine girer:
- Sessiz (silent) —
readOnlyHint: trueişaretli araçlar. Ajan bunları sayfayı okur gibi serbestçe çağırır. - Onaylı (confirm) — geri kalan her şey. Çalıştırmadan önce ajan, ziyaretçiye tam olarak ne yapmak üzere olduğunu — kesin argüman değerleriyle birlikte — söylemeli ve açık bir evet almalıdır. Bu yalnızca bir prompt değil, platform tarafından uygulanan bir kuraldır: ziyaretçi onayı kaydedilmemiş bir confirm çağrısı, sayfanıza hiç ulaşmadan reddedilir.
Varsayılan kademe, sizin annotation'larınızdan gelir. Widget bazında page_tools_config ile geçersiz kılabilirsiniz; PATCH /v1/widgets/{widget_id} ile ayarlanır (bkz. API referansı):
{
"allow_page_tools": true,
"page_tools_config": {
"silent": ["highlight_product"],
"confirm": ["get_cart_summary"]
}
}
Geçersiz kılma listeleri annotation'ları ezer; bir ad iki listede de geçiyorsa confirm kazanır. Annotation'ı ve geçersiz kılması olmayan araçlar confirm kademesindedir — güvenli varsayılan budur.
:::warning Gerçek eylemler confirm'de kalsın Etkisi ziyaretçinin ekranının ötesine geçen araçları — gönderimler, rezervasyonlar, ödemeler, silmeler — silent listesine almayın. Özellikle Beta sırasında, geri alınamayan her şey onay arkasında kalmalıdır. :::
Sorun giderme
Ajan araçlarımı görmüyor.
- İki anahtar da açık olmalı: widget'ta Allow page tools (WebMCP) ve snippet'te
data-page-tools="1". - Kontrol paneli ayarının canlı sayfalarınıza ulaşması ~30 saniyeyi bulabilir (widget'ın herkese açık yapılandırması kısa süreliğine önbelleklenir) — sayfayı yeniden yükleyin ve yeni bir widget oturumu başlatın; araçlar oturum başında keşfedilir.
- Tarayıcı konsoluna bakın: öznitelik ayarlıyken yükleyici, keşfettiği araçları listeleyen bir
[bess-page-tools]satırı yazar. Boş liste, kaydınızın ona ulaşmadığı anlamına gelir — köprü bloğunun widget'tan önce veya kısa süre sonra çalıştığını doğrulayın (iki sıra da çalışır; widget birkaç saniye boyunca yeniden yoklar). - Sayfa araçları şimdilik normal, prompt tabanlı bir ajan gerektirir — görsel konuşma akışı (conversation flow) oluşturucusuyla yapılmış ajanlar henüz desteklemiyor.
Bir araç listede yok. Muhtemelen kurallardan birini bozduğu için düşürüldü — en sık nedenler: adda tire, sınırı aşan açıklama veya user_confirmed adlı parametre. Tanımı düzeltin; düşürmeler kayda geçirilir, asla otomatik onarılmaz.
Ajan bir eylemi çalıştırmayı reddediyor. Tasarlandığı gibi çalışıyor: confirm kademesindeki araçlar, o konuşmada ziyaretçinin açık evet'ini gerektirir. Gerçekten salt okunur bir araç sürekli onay istiyorsa kaydına annotations: { readOnlyHint: true } ekleyin (veya page_tools_config içinde silent listesine alın).
Beta notları
Sayfa araçları yeni — tel protokolü kararlı, ama taze bir özelliğin pürüzlerini bekleyin. Geri alınamayan eylemleri confirm kademesinde tutun, kayıtlarınızı gerçek konuşmalarla test etmeden onlara güvenmeyin ve ajan bir aracı yanlış kullanırsa ya da bir kayıt beklenmedik davranırsa bize bildirin — Beta geri bildirimi, sıradaki sürümü doğrudan şekillendirir.