Bağımlılıklar
Makine çevirisi
Bu sayfa İngilizce dokümantasyondan otomatik olarak çevrildi; esas alınması gereken sürüm İngilizce sayfadır. Yanlış görünen bir şey varsa, nasıl bildireceğinizi Çeviriler sayfası açıklar.
Bir aracın argümanları modelden gelir. Bazı değerlerse asla modelden gelmemelidir: kayıtlarınızdan bakılan bir fiyat, yalnızca bir insanın verebileceği bir onay, modelin uydurarak yanlış yapabileceği her şey.
Bağımlılıklar, kendi fonksiyonlarınızın doldurduğu parametrelerdir. Parametreye tür açıklamasını eklersiniz, fonksiyonu belirtirsiniz; SDK da araç çalışmadan önce onu çağırır.
Bir bağımlılık bildirme
Parametrenin türünü Annotated[...] içine sarın ve Resolve(fn) ekleyin:
from typing import Annotated
from pydantic import BaseModel
from mcp.server import MCPServer
from mcp.server.mcpserver import Resolve
mcp = MCPServer("Bookshop")
INVENTORY = {"Dune": 7, "Neuromancer": 0}
class Stock(BaseModel):
title: str
copies: int
async def check_stock(title: str) -> Stock:
return Stock(title=title, copies=INVENTORY.get(title, 0))
@mcp.tool()
async def reserve_book(title: str, stock: Annotated[Stock, Resolve(check_stock)]) -> str:
"""Reserve a copy of a book."""
if stock.copies == 0:
return f"{title!r} is out of stock."
return f"Reserved {title!r} ({stock.copies - 1} copies left)."
check_stockbir çözümleyicidir: SDK'nınreserve_book'tan önce çalıştırdığı, dönüş değeristockargümanı hâline gelen sıradan bir fonksiyon.titleparametresi, aracın kendititleargümanıdır ve ada göre eşleştirilir. Çözümleyici, araç gövdesinin göreceği doğrulanmış değerin aynısını görür.- Araç gövdesi, zaten var olan bir
Stockile işe başlar. Araçta arama kodu yok, "ya yoksa" diye başlayan bir giriş yok.
Info
FastAPI kullandıysanız bu, Depends'in karşılığıdır. Aynı hamle, aynı gerekçe: fonksiyon
neye ihtiyacı olduğunu bildirir, framework bunu sağlar ve bağlantı tür açıklamasında durur.
Modele görünmez
tools/list'in reserve_book için bildirdiği giriş şeması şöyle:
{
"type": "object",
"properties": {
"title": {"title": "Title", "type": "string"}
},
"required": ["title"],
"title": "reserve_bookArguments"
}
Tek bir özellik. Context nesnesi sayfasındaki Context gibi, çözümlenmiş bir parametre de sizinle SDK arasındaki bir sözleşmedir: stock şemada yer almaz, modele ondan hiç söz edilmez ve yine de bir stock değeri gönderen istemci yok sayılır. Aracınızın alabileceği tek değer çözümleyicinin değeridir.
Asıl mesele de bu son kısım. Modelin sağlayamadığı bir parametre, modelin yanlış yapamayacağı bir parametredir.
Deneyin
Sunucuyu MCP Inspector ile çalıştırın:
uv run mcp dev server.py
reserve_book formunda tek bir title alanı var. stock hiçbir yerinde yok. Dune ile çağırın:
Reserved 'Dune' (6 copies left).
Araç gövdesi hiçbir şey aramadı: önce check_stock çalıştı, döndürdüğü Stock da argüman olarak geldi. Neuromancer'ı deneyin; aynı çözümleyici araca sıfır verir.
Tip
Araç gövdesinde doğrudan check_stock(title) da çağırabilirdiniz. Değer bir yardımcı fonksiyon
çağrısından fazlasını hak ettiğinde onu bağımlılık olarak bildirin: stok bilgisine ihtiyaç duyan
her araç aynı parametreyi bildirir ve kaç tanesi bildirirse bildirsin SDK çözümleyiciyi çağrı
başına en fazla bir kez çalıştırır. Sonraki bölümler geri kalanını ekler: birbirine bağımlı
çözümleyiciler ve kullanıcıya soru soran çözümleyiciler.
Bağımlılıkların bağımlılıkları
Bir çözümleyici, aynı tür açıklamasıyla kendi bağımlılıklarını bildirebilir:
from typing import Annotated
from pydantic import BaseModel
from mcp.server import MCPServer
from mcp.server.mcpserver import Resolve
mcp = MCPServer("Bookshop")
INVENTORY = {"Dune": 7, "Neuromancer": 0}
class Stock(BaseModel):
title: str
copies: int
async def check_stock(title: str) -> Stock:
return Stock(title=title, copies=INVENTORY.get(title, 0))
async def estimate_delivery(stock: Annotated[Stock, Resolve(check_stock)]) -> str:
return "tomorrow" if stock.copies > 0 else "in 2-3 weeks"
@mcp.tool()
async def order_book(
title: str,
stock: Annotated[Stock, Resolve(check_stock)],
delivery: Annotated[str, Resolve(estimate_delivery)],
) -> str:
"""Order a book from the shop."""
if stock.copies == 0:
return f"{title!r} is on backorder; it would arrive {delivery}."
return f"Ordered {title!r}; it arrives {delivery}."
estimate_delivery,check_stock'a bağımlıdır. SDK grafiği sırayla çalıştırır: önce stok, sonra tahmin, sonra araç.- Hem
stockhemdeliverysonuçtacheck_stock'a ihtiyaç duyar, ama o çağrı başına bir kez çalışır. Tek bir envanter sorgusu, iki tüketici. - Kaydedilecek hiçbir şey yok. Grafik, tür açıklamalarının ta kendisidir.
Check
Çağrı başına bir kez çalıştığına körü körüne inanmayın. check_stock'un içine bir print koyun
ve Inspector'dan order_book'u çağırın: çağrı başına tek satır. İki tüketici, tek sorgu.
SDK grafiği araç çağrıldığında değil, kaydedildiğinde analiz eder. Sınıflandıramadığı bir parametre (ne Context, ne Resolve(...), ne de bir araç argümanının adı) ve çözümleyiciler arasındaki bir döngü, her ikisi de başlangıçta InvalidSignature fırlatır. Sunucu, daha hiçbir istemci bağlanmadan başarısız olur; hataya yol açan parametre veya çözümleyici hata mesajında adıyla belirtilir.
Bir çözümleyicinin parametreleri tıpkı bir aracınkiler gibi çözümlenir: başka bir Resolve(...), ada göre aracın kendi argümanları veya Context (ctx.headers, lifespan (yaşam döngüsü) nesnesi, hepsi).
Warning
HTTP aktarımlarında Context, ctx.headers'ı da içerir. Başlıklar, her araç argümanı gibi
istemcinin sağladığı girdidir: bir yerel ayar veya özellik bayrağı için uygundur, kimlik için
asla. Çağıranın kim olduğu, herkesin ayarlayabileceği bir başlıktan değil, yetkilendirme
katmanınızdan gelir (Yetkilendirme).
Tip
Çağrı başına bir kez tam olarak bunu ifade eder: bir sonraki tools/call, check_stock'u
yeniden çalıştırır. Bir istekten uzun yaşaması gereken bir kaynağın (bir veritabanı havuzu, bir
HTTP istemcisi) yeri Lifespan sayfasıdır; bir çözümleyici ona
ctx.request_context.lifespan_context üzerinden ulaşabilir.
Yalnızca gerektiğinde sormak
Bir çözümleyici yanıtı bilmek zorunda değildir. Elicit(message, Model) döndürebilir; SDK da kullanıcıya sorar. Bu, sizin yerinize çalıştırılan Elicitation (kullanıcıdan bilgi isteme) mekanizmasıdır:
from typing import Annotated
from pydantic import BaseModel, Field
from mcp.server import MCPServer
from mcp.server.mcpserver import Elicit, Resolve
mcp = MCPServer("Bookshop")
INVENTORY = {"Dune": 7, "Neuromancer": 0}
class Stock(BaseModel):
title: str
copies: int
class Backorder(BaseModel):
confirm: bool = Field(description="Order anyway and wait?")
async def check_stock(title: str) -> Stock:
return Stock(title=title, copies=INVENTORY.get(title, 0))
async def confirm_backorder(
title: str,
stock: Annotated[Stock, Resolve(check_stock)],
) -> Backorder | Elicit[Backorder]:
if stock.copies > 0:
return Backorder(confirm=True) # in stock: nothing to ask
return Elicit(f"{title!r} is out of stock (2-3 weeks). Order anyway?", Backorder)
@mcp.tool()
async def order_book(
title: str,
stock: Annotated[Stock, Resolve(check_stock)],
backorder: Annotated[Backorder, Resolve(confirm_backorder)],
) -> str:
"""Order a book from the shop."""
if not backorder.confirm:
return "No order placed."
if stock.copies == 0:
return f"Backordered {title!r}; it ships in 2-3 weeks."
return f"Ordered {title!r}."
- Stokta varsa:
confirm_backorderdoğrudan birBackorderdöndürür. Soru yok, gidiş-dönüş yok. Kullanıcı yalnızca yanıtı önemli olduğunda rahatsız edilir. - Stokta yoksa: SDK elicitation'ı gönderir, yanıtı
Backorder'a göre doğrular ve enjekte eder. Çözümleyiciniz protokole hiç dokunmaz. - Araç,
backorder.confirm'ü diğer argümanlar gibi okur. Hayır yanıtı da bir yanıttır: elicitationconfirm=Falseile kabul edilir, araç çalışır ve sipariş verilmez. Sormak, araç gövdesindeki bir tesisat işi değil, bir ön koşul hâline geldi.
Peki ya kullanıcı hiç yanıt vermezse, yani soruyu reddeder veya iptal ederse?
Check
Neuromancer için order_book'u çalıştırın ve soruyu reddedin. Tür açıklaması
Annotated[Backorder, Resolve(...)] biçiminde yazıldığında araç gövdesi hiç çalışmaz; çağrı,
modelin okuyabileceği bir hata sonucuyla başarısız olur:
Error executing tool order_book: Resolver for parameter 'backorder' could not resolve: elicitation was decline
Bir ön koşul için doğru varsayılan budur: yanıt yoksa sipariş de yok. Reddetme, aracınızın ele almak istediği bir sonuçsa (ön siparişi atlayıp yine de başka bir kitap önermek gibi) tür açıklamasını bunun yerine ElicitationResult[Backorder] olarak yazın; araç, dallanabileceği tam accept/decline/cancel sonucunu alır. Elicitation sayfası bu biçimi ve sormaya dair diğer her şeyi gösterir: şema kuralları, üç yanıt, konuşmanın istemci tarafı.
Info
Framework, sorunun aktarımını anlaşılan protokol sürümüne göre seçer; yukarıdaki kod her
ikisinde de aynıdır. 2026-07-28 ve sonrasında soru, çok turlu (multi-round-trip) bir
tools/call içinde taşınır: sunucu soruyu döndürür, istemcinin elicitation_callback'i
yanıtlar ve Client çağrıyı sizin yerinize yeniden dener
(Çok turlu istekler). 2025-11-25 ve öncesinde ise çağrının
ortasında senkron bir elicitation isteğidir. Her soru çağrı başına tam olarak bir kez sorulur;
bu, çözümleyici hakkında değil soru hakkında bir garantidir. Çok turlu biçimde, çağrı bir sorudan
sonra her sürdüğünde herhangi bir çözümleyici yeniden çalışabilir; dolayısıyla
return Elicit(...) satırından önceki kod bu turların her birinde çalışır. Kaydedilmiş yanıt,
tekrarlanan soruyu kullanıcıya yeniden sormadan karşılar. Kaydedilmiş bir yanıta yalnızca
çözümleyici soru sorduğunda başvurulur; check_stock gibi sormadan yanıt veren bir
çözümleyici her zaman kendi hesapladığı değeri sağlar. Her yanıt kendi sorusuyla
eşleştirildiğinden, elicitation yapan bir çözümleyici sorusunu aracın argümanlarından ve önceki
yanıtlardan deterministik olarak türetmelidir. Çağrı başına üretilen bir değer (bir
default_factory kimliği, bir zaman damgası) her turda yeniden türetilir ve yanıtın bağlanması
gereken bir soruda yer almamalıdır. Bu tür uçucu verilerden kurulan bir soru, kaydedilmiş her
yanıtı bayat gösterir; bu yüzden sunucu, istemcinin tur sınırı çağrıyı sonlandırana kadar
soruyu her turda yeniden sorar.
Kullanıcıya değil, istemciye sormak
Elicitation, bir çözümleyicinin sorabileceği üç sorudan biridir ve çok turlu akış başkasına izin vermez. Diğer ikisi kullanıcıya değil istemciye gider: istemci üzerinden bir LLM çağrısı çalıştırmak için Sample(...) (bir sampling/createMessage isteği), istemcinin güncel kök dizinlerini (roots) almak için ListRoots() döndürün. Hiçbirinin accept/decline sonucu yoktur; tüketici, tür açıklaması olarak doğrudan sonuç türünü yazar: CreateMessageResult (istek tools veya tool_choice taşıdığında CreateMessageResultWithTools) ya da ListRootsResult:
from typing import Annotated
from mcp.server import MCPServer
from mcp.server.mcpserver import Resolve, Sample
from mcp.types import CreateMessageResult, SamplingMessage, TextContent
mcp = MCPServer("Bookshop")
def suggest_title(genre: str) -> Sample:
prompt = f"Suggest one {genre} book title. Answer with the title only."
return Sample(
[SamplingMessage(role="user", content=TextContent(type="text", text=prompt))],
max_tokens=50,
)
@mcp.tool()
async def recommend_book(
genre: str,
suggestion: Annotated[CreateMessageResult, Resolve(suggest_title)],
) -> str:
"""Recommend a book in the given genre."""
title = suggestion.content.text if suggestion.content.type == "text" else "the classics"
return f"Today's {genre} pick: {title}"
- Framework bunları tıpkı
Elicitgibi yönlendirir: 2026-07-28 sürümünde çok turlutools/calliçinde, 2025-11-25 sürümünde bağımsız sunucu->istemci isteği üzerinden. Bildirilmemiş bir yetenek, çağrıyı-32021protokol hatasıyla reddeder (sampling,roots, form kipindeelicitation; istektoolsveyatool_choicetaşıdığındasampling.tools). - Yukarıdaki bilgi kutusunun sorular hakkında söylediği her şey aynen geçerlidir: bir
Sampleisteği, kaydedilmiş sonucuyla birebir gösterimine göre eşleştirilir; bu yüzden onu aracın argümanlarından ve önceki yanıtlardan deterministik olarak kurun. Böylece istemci, LLM çağrısının bedelini tur başına değil araç çağrısı başına bir kez öder. Kaydedilmiş sonuç çağrının geri kalanındarequest_stateiçinde taşınır; bu nedenle çok büyük bir tamamlama, kalan her gidiş-dönüşü ağırlaştırır. - Bağımsız örnekleme (sampling) ve kök dizinler özellikleri 2026-07-28 itibarıyla kullanım dışı bırakıldı (SEP-2577). İstemcinin modeline ihtiyaç duyan yeni sunucular bu taşıyıcı üzerinden sorar; duymayanlar doğrudan bir LLM sağlayıcısıyla entegre olmalıdır.
"none"dışındakiinclude_contextdeğerlerinin kendisi de kullanım dışıdır; bunları kullanmayın.
Özet
- Bir araç parametresinde
Annotated[T, Resolve(fn)]: SDKfn'i çalıştırır ve dönüş değerini enjekte eder. - Çözümlenmiş bir parametre modele görünmez; istemci de onu sağlayamaz. Modelin uydurmaması gereken değerlerin (fiyatlar, kimlikler, izinler) yeri burasıdır.
- Bir çözümleyicinin parametreleri de aynı şekilde çözümlenir:
Context, başka birResolve(...)veya ada göre bir araç argümanı. Grafik, kaç tüketicisi olursa olsun her çözümleyiciyi tur başına en fazla bir kez çalıştırır; her soru tam olarak bir kez sorulur ve çağrı bir sorudan sonra sürdüğünde herhangi bir çözümleyici yeniden çalışabilir. - Hatalı grafikler çağrı ortasında değil, kayıt sırasında
InvalidSignatureile başarısız olur. - Kullanıcıya sormak için, yalnızca mecbur kaldığınızda,
Elicit(message, Model)döndürün. Sarmalanmamış tür açıklamaları reddedildiğinde çağrıyı iptal eder;ElicitationResult[T]aracın dallanmasına izin verir. - İstemciden bir LLM tamamlaması veya kök dizin listesi istemek için
Sample(...)ya daListRoots()döndürün; yalın sonuç enjekte edilir.
Sunucunuzun başlangıçta bir kez kurduğu durum ve bir işleyicinin ona nasıl ulaştığı Lifespan sayfasının konusudur.