Yapay zeka ajanları ve MCP

Yapay zekâ ajanları için e-posta: insansız kayıt doğrulama

Gezinebilen, form dolduran ve ödeme yapabilen bir ajan bile "kodu e-postanızdan kontrol edin" karşısında donup kalır, çünkü okuyabileceği bir gelen kutusu yoktur. İşte ona hesap ve anahtar gerektirmeyen bir tane vermenin yolu — bloklayan bir beklemeyle MCP üzerinden, ya da LangChain, OpenAI Agents SDK ve Vercel AI SDK için düz bir araç fonksiyonu olarak — bir kaydı sonuna kadar taşıyan dört adımlı döngü ve gözetimsiz bir ajanın ihtiyaç duyduğu korkuluklarla birlikte.

  • Orta düzey
  • 21 dk okuma
Gri bir masada mavi bir zarfı yukarı kaldıran, bir eli klavyede duran küçük gri bir robot ve üstünde süzülen mavi bir onay işareti

Her ajanın çarptığı duvar

Bir deneme sürümüne kaydolun, bir çalışma alanı oluşturun, bir API anahtarı alın, bir betaya katılın: bunların hepsi bir formda biter ve her form bir posta kutusunda biter. Bir insan telefonuna göz atar. Bir ajanın göz atacağı hiçbir şey yoktur — okuyabileceği bir adresi yoktur ve makul olanlar durup sizden kodu ister, bu da bir ajan göndermenin amacını boşa çıkarır.

Çözüm daha akıllıca bir prompt değildir. Ajanın programatik olarak okuyabileceği, önce oluşturulacak bir hesabı olmayan (bir posta hesabı oluşturan bir ajan, bir kat aşağıda aynı duvarla karşılaşır) ve yönetilecek bir anahtarı olmayan bir gelen kutusudur. Tek kullanımlık bir posta kutusu tam olarak budur: posta ona ulaştığı anda bir posta kutusu var olur ve onu okumak tek bir HTTP isteğidir.

Ona bir gelen kutusu vermenin iki yolu

Aynı posta kutusuna iki şekilde ulaşılabilir ve seçim, posta kutusuna değil ajanın nasıl inşa edildiğine bağlıdır:

MCP sunucusuREST araç fonksiyonu
Uyduğu durumAjan bir MCP istemcisinde çalışıyor — Claude Code, Cursor, Claude Desktop ya da bir MCP adaptörü olan bir framework.Ajanı kodla siz yazıyorsunuz: LangChain, OpenAI Agents SDK, Vercel AI SDK ya da kendi döngünüz.
Beklemewait_for_message, sunucu tarafında en fazla 25 saniye boyunca bloklar ve mesajı tam olarak döndürür. Beklerken sıfır token harcanır.Araç fonksiyonu, son tarihine kadar saniyede bir döngüye girer. Bu da sıfır token — döngü modelde değil, sizin kodunuzdadır.
Kurulumİstemcinin yapılandırmasında tek bir URL. Kod yok.İki fonksiyon, kırk satır, bir HTTP kütüphanesi.
Modelin gördüğü şeyAçıklamalı altı araç, artı sunucunun bağlantı anında gönderdiği bir talimat paragrafı.Araç açıklamalarınızın ne dediği. Aşağıdaki docstring'ler tam olarak bunlar olacak şekilde yazılmıştır.

MCP yolu tek bir satırdır ve istemci başına kurulum rehberinde yedi istemci için tam satır var; bir yapay zekâ ajanının okuyabileceği bir gelen kutusu ise araçları derinlemesine anlatır. Bu rehberin geri kalanı, kendi inşa ettiğiniz ajanlar için REST yoludur.

MCP yolu, tam olarak
{"mcpServers":{"grabmail":{"url":"https://grabmail.io/mcp"}}}

Bir araç fonksiyonu: LangChain ve OpenAI Agents SDK için Python

Özenli docstring'leri olan iki düz fonksiyon. Docstring'ler koddan daha önemlidir: her iki framework'te de modelin, aracı ne zaman çağıracağına ve yanıtla ne yapacağına karar vermek için okuduğu açıklama haline gelirler, bu yüzden bir ajanın yanlış yaptığı iki şeyi söylerler — takma ad ile adres arasındaki fark, ve timed_out'un ne anlama geldiği.

inbox_tools.py
"""inbox_tools.py — two plain functions any agent framework can wrap. No key, no account."""
import secrets
import time

import requests

API = "https://grabmail.io/api/v1"


def create_inbox() -> dict:
    """Create a fresh disposable email inbox for this task.

    Returns the ADDRESS to poll and the ALIAS to give to websites. Put the alias
    into forms; never hand out the address. Nothing is created server-side.
    """
    address = f"agent-{secrets.token_hex(4)}@grabmail.io"
    r = requests.get(f"{API}/mailbox", params={"address": address}, timeout=15)
    r.raise_for_status()
    return {
        "address": address,
        "alias": r.json().get("alias"),
        "next_step": "Put the alias into the form. Then call wait_for_message with the address.",
    }


def wait_for_message(address: str, subject_contains: str = "", timeout_seconds: int = 60) -> dict:
    """Wait for an email to arrive at the address, up to timeout_seconds.

    Returns the message (from, subject, text, html) or {"status": "timed_out"}.
    On timed_out, call again — up to three times — before concluding no mail was sent.
    """
    deadline = time.monotonic() + timeout_seconds
    while time.monotonic() < deadline:
        r = requests.get(f"{API}/mailbox", params={"address": address}, timeout=15)
        if r.status_code == 429:                       # slow down, do not fail
            time.sleep(float(r.headers.get("Retry-After", 1)))
            continue
        r.raise_for_status()
        for m in r.json()["messages"]:
            if subject_contains.lower() in m["subject"].lower():
                full = requests.get(f"{API}/message/{m['id']}", params={"mailbox": address}, timeout=15)
                full.raise_for_status()
                return full.json()
        time.sleep(1)                                   # one read a second, never throttled
    return {"status": "timed_out", "hint": "Call again, up to three times, before giving up."}

Onları sarmalamak, framework başına tek bir çağrıdır. LangChain'in tool'u docstring'i ve tip ipuçlarını okur; Agents SDK'nın function_tool'u aynısını yapar ve araçları, talimatları döngüyü tekrarlayan bir ajana ekler:

LangChain
# LangChain: the docstring becomes the tool description the model reads.
from langchain_core.tools import tool

create_inbox_tool = tool(create_inbox)
wait_for_message_tool = tool(wait_for_message)
# agent = create_react_agent(model, tools=[create_inbox_tool, wait_for_message_tool, ...])
OpenAI Agents SDK
# OpenAI Agents SDK: same two functions, same docstrings.
from agents import Agent, Runner, function_tool

signup_agent = Agent(
    name="Signup agent",
    instructions=(
        "When a site needs an email address, call create_inbox once. Put the ALIAS in the form. "
        "Right after submitting, call wait_for_message with the ADDRESS and a word from the expected "
        "subject. If it returns timed_out, call it again, up to three times."
    ),
    tools=[function_tool(create_inbox), function_tool(wait_for_message)],
)

result = Runner.run_sync(signup_agent, "Sign up for a trial at https://app.example.com/signup and report the login.")
print(result.final_output)

Aynı araç TypeScript'te, Vercel AI SDK için

AI SDK'nın tool()'u bir açıklama, bir şema ve bir execute alır; açıklama aynı iki cümleyi taşır. Her iki aracı da dörtten yüksek bir maxSteps ile generateText ya da streamText'e geçirin, çünkü döngü dört araç çağrısı uzunluğundadır:

inbox-tools.ts
// inbox-tools.ts — the same two tools for the Vercel AI SDK (v5 shape: inputSchema + execute).
import { tool } from 'ai';
import { z } from 'zod';

const API = 'https://grabmail.io/api/v1';
const sleep = (ms: number) => new Promise(r => setTimeout(r, ms));

export const createInbox = tool({
  description: 'Create a fresh disposable email inbox for this task. Returns the ADDRESS to poll and the ALIAS to give to websites. Put the alias into forms; never hand out the address.',
  inputSchema: z.object({}),
  execute: async () => {
    const address = `agent-${crypto.randomUUID().slice(0, 8)}@grabmail.io`;
    const res = await fetch(`${API}/mailbox?address=${encodeURIComponent(address)}`);
    const { alias } = (await res.json()) as { alias: string | null };
    return { address, alias, next_step: 'Put the alias into the form. Then call waitForMessage with the address.' };
  },
});

export const waitForMessage = tool({
  description: 'Wait for an email to arrive at the address, up to timeoutSeconds. Returns the message (from, subject, text, html) or { status: "timed_out" }. On timed_out, call again — up to three times — before concluding no mail was sent.',
  inputSchema: z.object({
    address: z.string(),
    subjectContains: z.string().optional(),
    timeoutSeconds: z.number().int().min(5).max(120).default(60),
  }),
  execute: async ({ address, subjectContains = '', timeoutSeconds }) => {
    const deadline = Date.now() + timeoutSeconds * 1000;
    while (Date.now() < deadline) {
      const res = await fetch(`${API}/mailbox?address=${encodeURIComponent(address)}`);
      if (res.status === 429) { await sleep(Number(res.headers.get('retry-after') ?? 1) * 1000); continue; }
      if (!res.ok) throw new Error(`GET /mailbox answered ${res.status}`);
      const { messages } = (await res.json()) as { messages: { id: string; subject: string }[] };
      const hit = messages.find(m => m.subject.toLowerCase().includes(subjectContains.toLowerCase()));
      if (hit) return (await fetch(`${API}/message/${hit.id}?mailbox=${encodeURIComponent(address)}`)).json();
      await sleep(1000);
    }
    return { status: 'timed_out', hint: 'Call again, up to three times, before giving up.' };
  },
});

Şekil, araç kavramı olan her framework'te aynıdır: modelin okuduğu bir açıklama, argümanlar için bir şema ve sizin tarafınızda çalışan bir fonksiyon. Taşınması gereken iki şey takma ad cümlesi ve timed_out cümlesidir; geri kalan her şey Node rehberinden ya da Python rehberinden gelen istemcidir.

Dört adımda döngü

Framework ne olursa olsun, bir kayıt aynı dört araç çağrısıdır ve ajana bunu kendi keşfetmesi değil, talimatlarında söylenmesi gerekir:

  1. Görev başına bir kez create_inbox. Karşılığında bir adres, bir takma ad ve hangisinin hangisi olduğunu söyleyen bir cümle gelir.
  2. Takma ad forma girer. Gönderin.
  3. Adres üzerinde, hemen, wait_for_message, onay postasının taşıyacağı bir kelimeye ayarlanmış subject_contains ile — "code", "verify", "confirm". Bir zamanlayıcıyla değil, başka bir işten sonra da değil: posta zaten yoldadır.
  4. Kod, beklemenin döndürdüğü mesajdan çıkar; ajan onu yazar ya da bağlantıyı izler. Şablonun kullandığı kelimelerden sonra altı hane — otomatik testlerde OTP kodları rehberinde, bir teste uyduğu gibi bir ajana da uyan ayıklama kuralları var.

timed_out döndüren altmış saniyelik bir bekleme bir başarısızlık değildir; "henüz değil" demektir. Talimatlar şunu söylemelidir: en fazla üç kez yeniden çağır. Üç çağrı üç dakikayı aşar, ki bu gerçekten gönderilmiş herhangi bir işlemsel postayı kapsar — ve ajana, formun bir hata gösterdiğini ya da takma adı yanlış yazdığını fark etmesi için üç şans verir.

Takma ad kuralı

Her posta kutusunun iki adresi vardır. Adres, ajanın okumak için kullandığı adrestir; ona sahip olan herkes posta kutusunu açabilir, çünkü hesap yoktur ve adres tek anahtardır. Takma ad ise ayrı bir alan adında, aynı posta kutusuna teslimat yapan ama onu okumak için kullanılamayan ikinci bir adrestir.

Yani site takma adı alır, ajan ise adresi elinde tutar. Adresi bir forma yapıştıran bir ajan, siteye — ve sitenin onu sızdırdığı herkese — ajanın orada bundan sonra alacağı her mesajı okuma yetisini vermiş olur. Yukarıdaki araç fonksiyonları ikisini de, hangisinin nereye gideceğini söyleyen bir next_step ile döndürür ve talimatlar bunu tekrarlar, çünkü iki kez söylenen bir kural, uyulan bir kuraldır.

alias@examplewhat you hand overGrabMailroutes it to the mailboxyou@examplewhat you keepRead it back and you getan empty mailbox. Always.Read it back and you getevery message.The service you signed up to only ever holds the left-hand one.
Takma ad, ajanın dağıttığı ve geri okuduğunda boş bir posta kutusu bulduğu şeydir; adres ise elinde tuttuğu ve geri okuduğunda her mesajı bulduğu şeydir.

Gözetimsiz bir ajan için korkuluklar

Bir test paketi başarısız olur ve durur. Bir durumu yanlış okuyan bir ajan ise devam eder, ve harcamaya devam eder. Beş sınır, bir e-posta adımının bir görevin pahalı kısmına dönüşmesini engeller:

Görev başına bir gelen kutusu
Bir adresi görevler ya da çalıştırmalar arasında asla yeniden kullanmayın. İçinde inandırıcı bir kod bulunan eski bir mesaj, bir ajanın kendinden emin biçimde yanlış şeyi yapmasının en hızlı yoludur. create_inbox'ın hiçbir bedeli yoktur; her seferinde çağırın.
Bir bekleme bütçesi
wait_for_message'a üç çağrı, ardından durup rapor verin. Hiç gönderilmemiş bir postayı süresiz bekleyen bir ajan, bir worker yuvasını ve bir faturayı tüketir.
Adımın tamamı için bir son tarih
Gönderimden koda kadar uçtan uca beş dakika. Bunu aştığında doğru eylem, formu yeniden denemek değil, bir insana ne olduğunu söylemektir.
Konu filtreleme
Her zaman subject_contains geçirin. Kod postasından önce ulaşan bir hoş geldin postası, aksi halde "o mesaj" olur ve ajan bir pazarlama altbilgisinden altı hane ayıklamaya çalışır.
Adresi günlüğe kaydetme
Adresi görev günlüğüne yazın. Mesajlar 5 gün kalır, böylece bir insan daha sonra posta kutusunu açıp ajanın tam olarak ne gördüğünü görebilir — bir çalıştırma ters gittiğinde en yararlı tek şey budur.

Tarayıcı ajanları

Gerçek bir tarayıcıyı süren bir ajan — Browser Use, bir Playwright MCP sunucusu, bir computer-use modeli — e-posta adımının en sert ısırdığı durumdur, çünkü formla kimsenin planladığından önce karşılaşacaktır. Üç şey bunu çalışır kılar:

  • Ona her iki sunucuyu da verin. Tarayıcı araçları ve gelen kutusu araçları aynı oturumda, böylece "e-postanızı kontrol edin" bir çıkmaz değil bir araç çağrısı olur.
  • Döngüyü sistem promptuna koyun. Dört satır: ilk e-posta alanında bir gelen kutusu oluşturun; takma adı forma girin; gönderimin hemen ardından adres üzerinde bekleyin; timed_out'ta üç yeniden deneme.
  • Reddedilmeyi bekleyin. Takma adın alan adını reddeden bir form, bunu sayfada söyler; ajan hatayı okuyup durmalı, isim denemeye devam etmemelidir. Bir reddi aşmanın dürüst yolları — kendi alan adınız ya da kara listelerin dışında tutulan havuzdan bir alan adı — ajanın değil, sizin verdiğiniz yapılandırma kararlarıdır.

Ajanın kendi okuması için site, bu sayfanın söylediklerini bir modelin tercih ettiği biçimde söyleyen düz metin bir harita olan llms.txt'i ve kod yazan bir ajanın istemciyi üzerine inşa edebileceği bir OpenAPI belgesini yayımlar.

Gözetimsiz çalıştırmasına izin vermeden önce

  • Takma ad kuralını ve timed_out'un ne anlama geldiğini belirten araç açıklamaları.
  • Dört adımlı döngüyü ve üç beklemelik bir bütçeyi içeren talimatlar.
  • Görev başına bir kez çağrılan, asla yeniden kullanılmayan create_inbox.
  • Her beklemede subject_contains.
  • Görev günlüğüne yazılan adres.
  • Gelen kutusuna gizli hiçbir şeyin gönderilmesini yasaklayan bir satır.

Bir ajanın ihtiyaç duyduğu her şey bu kadar. Aynı gelen kutusu gündüz bir test paketine, gece bir ajana hizmet eder, çünkü altında yatan aynı üç HTTP çağrısıdır — ve ajanın kaydolduğu site genel alan adlarını reddederse, kendi alan adınız ya da listelerin dışında tutulan havuz, araçlarda hiçbir değişiklik yapmadan devreye girer.

Sorular

Ajan için bir API anahtarına ihtiyacım var mı?

Hayır. Genel alan adları, REST ve MCP üzerinden aynı şekilde, ne anahtar ne hesap ne de başlık ister. Yalnızca tek kullanımlık posta kara listelerinin dışında tutulan ücretli alan adı havuzu bir bearer token kullanır, ve araç fonksiyonları bunun dışında onun için de aynıdır.

MCP mi yoksa bir REST aracı mı — hangisini seçmeliyim?

Ajan zaten bir MCP istemcisinde yaşıyorsa, MCP: tek bir satırdır ve bekleme sunucu tarafındadır. Ajanı bir framework içinde yazıyorsanız, bir araç fonksiyonu: kırk satırdır, herhangi bir modelle çalışır ve modelin okuduğu açıklamayı siz denetlersiniz. İkisi de aynı posta kutusuna ulaşır.

Bekleme, token cinsinden ne kadara mal olur?

Her iki durumda da hiçbir şeye. MCP beklemesi sunucuda bloklar; REST aracı sizin kodunuzda döngüye girer. Model, token'ları araç çağrısına ve sonucu okumaya harcar, aradaki altmış saniyeye değil — bir modelin bir posta kutusunu kendi başına yoklamasına izin vermemenin bütün nedeni de budur.

Birden fazla ajan aynı anda çalışabilir mi?

Evet. Her görev kendi gelen kutusunu alır ve bir oturum durumu yoktur. Sınırlar, REST üzerinden adres başına saniyede bir okuma ve istemci başına dakikada 1200 istek, MCP üzerinden ise 8 eşzamanlı wait_for_message çağrısıdır — bunun ötesinde araç anında timed_out yanıtı verir ve ajan yeniden çağırır.

Ajan, gelen kutusundan e-posta gönderebilir mi?

Hayır. Hizmet, tasarım gereği yalnızca alır — gönderim yapabilen, hesapsız ücretsiz bir gelen kutusu bir saat içinde bir spam aktarıcısına dönüşürdü. Posta göndermesi gereken bir ajanın bir gönderim sağlayıcısına ve kendi kimlik bilgilerine ihtiyacı vardır.

Site, takma adın alan adını reddederse ne olur?

O zaman bir tek kullanımlık alan adı kara listesindedir ve @ işaretinin önündeki hiçbir isim bunu değiştirmez. Sahibi olduğunuz bir alan adını hizmete yönlendirin (tek bir MX kaydı, ücretsiz) ya da listelerin dışında tutulan ücretli havuzdan bir alan adı kullanın — ikisi de alan adı sabitini değiştirerek aynı araçlara takılır. Kayıt formları neden tek kullanımlık e-postayı engeller rehberi, hangi kontrolün sizi reddettiğini anlatır.

Gelen kutusu ajanıma özel mi?

Hayır. Adresi bilen herkes onu okuyabilir; takma adın var olma nedeni de budur ve oraya asla gizli hiçbir şey gönderilmemesi gerekmesinin nedeni de budur. On dakika yaşayan bir kod için bu sorun değildir; bu, ajanın talimatlarının açıkça belirtmesi gereken tek kuraldır.

Henüz tazeyken deneyin

Bir adres tek tıkla alınır, hesap ve kart gerekmez. Bu rehberdeki her şey onunla hemen çalışır.

Tekrar hoş geldiniz

Kutularınız ve alan adlarınız tek bir yerde.