Abonelikler
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 sunucunun kataloğu sabit değildir. Çalışma zamanında yeni araçlar ortaya çıkar, bir kaynak URI'sinin arkasındaki içerik değişir. İstemci bundan client.listen(...) aracılığıyla haberdar olur: yanıtı akışın kendisi olan tek bir subscriptions/listen isteği. Akış açık kalır ve istemcinin istediği değişiklik bildirimlerini taşır.
Bu sayfa işin istemci tarafını anlatır: akışı açma, ana iş akışınızın yanında izleme ve sonlanmalarını ele alma. Değişiklikleri yayımlama, filtreleme ve yöntemi sunma ise hikâyenin sunucu tarafıdır; İşleyicinin içinde bölümündeki Abonelikler sayfasında anlatılır. Buradaki örnekler orada kurulan sprint panosu sunucusuyla konuşur.
Akışı izleme
Bir abonelik tek bir bağlam yöneticisidir. İçine girmek isteği gönderir (anahtar sözcük argümanlarınız abonelik filtresi olur) ve sunucunun onayını bekler; böylece blok başladığında akış canlıdır.
from mcp import Client
from mcp.client.subscriptions import ResourceUpdated, ToolsListChanged
from mcp.types import TextResourceContents
BOARD = "board://sprint"
async def read_board(client: Client, uri: str = BOARD) -> str:
[contents] = (await client.read_resource(uri)).contents
assert isinstance(contents, TextResourceContents)
return contents.text
async def follow_board(client: Client) -> None:
async with client.listen(tools_list_changed=True, resource_subscriptions=[BOARD]) as sub:
async for event in sub:
match event:
case ResourceUpdated(uri=uri):
print(await read_board(client, uri))
case ToolsListChanged():
tools = await client.list_tools()
print("tools:", [tool.name for tool in tools.tools])
case _:
pass # kinds the filter did not ask for never arrive
async def main() -> None:
async with Client("http://localhost:8000/mcp") as client:
await follow_board(client)
Yineleme türü belli dört olay üretir: ToolsListChanged, PromptsListChanged, ResourcesListChanged ve ResourceUpdated(uri=...).
Bir olay neyin değiştiğini söyler, asla nasıl değiştiğini değil. follow_board'un read_resource ve list_tools'u çağırmasının nedeni budur: olay, yeniden getirmek için bir işarettir. Hangi kaynağın değiştiğini varsaymak yerine event.uri alanını okuyun: bir filtre birkaç URI sayabilir ve sunucu bunlardan birinin alt kaynağındaki bir değişikliği bildirebilir.
Tüketilmeyi bekleyen yinelenen olaylar tek bir olaya indirgenir; yeniden getirdiğinizde yine güncel durumu alırsınız. Yalnızca özdeş olaylar birleşir: farklı URI'ler için iki ResourceUpdated, iki ayrı olaydır.
Tutamacın (handle) iki özelliği daha var:
sub.honored, sunucunun onayladığı filtredir: geçirdiğiniz alanları taşıyan ve öznitelik olarak okunan birSubscriptionFilter(sub.honored.prompts_list_changed).MCPServeristediğiniz her türü kabul eder, bu yüzden isteğinizi olduğu gibi geri yansıtır. Daha az tür destekleyen bir sunucu daha azını onaylar; onaylanmış bir tür yine de hiç tetiklenmeyebilir. Sunucu isteği onaylamak yerine tümüyle reddedebilir de (sunucu sayfasındaki Kimin izleyebileceğine karar verme bölümüne bakın); bu, isteğin hatası olarak yüzeye çıkar.sub.subscription_id, listen isteğinin kimliğidir; bu akışın her çerçevesine damgalanan kimlik budur. Aynı anda birkaç abonelik açık olabilir; her biri kendi kimliğiyle ayrıştırılır.
Engellemeden izleme
follow_board, sunucu akışı kapatana kadar çalışır, ki bu hiç olmayabilir; bu yüzden tek başına bırakıldığında programınızı ele geçirir. Gerçek istemciler izleyiciyi ana iş akışının yanında ister: bir izleyici bir önbelleği ya da arayüzü güncel tutarken ajan araçları çağırır.
Önce aboneliği açın, ardından izleyiciyi başlatın ve işinize devam edin.
import asyncio
from mcp import Client
from mcp.client.subscriptions import Subscription
from .tutorial003 import BOARD, read_board
async def watch(client: Client, sub: Subscription) -> None:
async for _event in sub:
board = await read_board(client)
print(board)
if "[ ]" not in board:
return # sprint finished: the stream closes when run_sprint leaves the block
async def run_sprint(client: Client) -> None:
async with client.listen(resource_subscriptions=[BOARD]) as sub:
print(await read_board(client)) # snapshot: acknowledged, so nothing after this is missed
watcher = asyncio.create_task(watch(client, sub))
for task in ("design", "build", "ship"):
await client.call_tool("complete_task", {"board": "sprint", "task": task})
await watcher # returns once the watcher has seen the finished board
async def main() -> None:
async with Client("http://localhost:8000/mcp") as client:
await run_sprint(client)
if __name__ == "__main__":
asyncio.run(main())
import trio
from mcp import Client
from mcp.client.subscriptions import Subscription
from .tutorial003 import BOARD, read_board
async def watch(client: Client, sub: Subscription) -> None:
async for _event in sub:
board = await read_board(client)
print(board)
if "[ ]" not in board:
return # sprint finished: the stream closes when run_sprint leaves the block
async def run_sprint(client: Client) -> None:
async with client.listen(resource_subscriptions=[BOARD]) as sub:
print(await read_board(client)) # snapshot: acknowledged, so nothing after this is missed
async with trio.open_nursery() as nursery:
nursery.start_soon(watch, client, sub)
for task in ("design", "build", "ship"):
await client.call_tool("complete_task", {"board": "sprint", "task": task})
async def main() -> None:
async with Client("http://localhost:8000/mcp") as client:
await run_sprint(client)
if __name__ == "__main__":
trio.run(main)
import anyio
from mcp import Client
from mcp.client.subscriptions import Subscription
from .tutorial003 import BOARD, read_board
async def watch(client: Client, sub: Subscription) -> None:
async for _event in sub:
board = await read_board(client)
print(board)
if "[ ]" not in board:
return # sprint finished: the stream closes when run_sprint leaves the block
async def run_sprint(client: Client) -> None:
async with client.listen(resource_subscriptions=[BOARD]) as sub:
print(await read_board(client)) # snapshot: acknowledged, so nothing after this is missed
async with anyio.create_task_group() as tg:
tg.start_soon(watch, client, sub)
for task in ("design", "build", "ship"):
await client.call_tool("complete_task", {"board": "sprint", "task": task})
async def main() -> None:
async with Client("http://localhost:8000/mcp") as client:
await run_sprint(client)
if __name__ == "__main__":
anyio.run(main)
Note
app.py, BOARD ve read_board'u ilk örnekten içe aktarır; bu depo o örneği
tutorial003.py olarak saklar. Oluşturulan dosyaları client.py ve app.py adlarıyla yan yana
kaydederseniz bunun yerine from client import BOARD, read_board yazın. Aşağıdaki watch.py
örneği de read_board'u aynı şekilde içe aktarır.
Önemli olan sıradır. Hiçbir şey yeniden oynatılmaz; bu yüzden akışınız var olmadan önce yayımlanan bir olay kaçar. client.listen(...) bloğuna girmek onayı bekler; dolayısıyla o andan itibaren her değişiklik izleyicinize ulaşır ve blok içinde aldığınız anlık görüntü hiçbirini kaçıramaz.
Açık bir akışın yanında istekler, izleyici görevinden de başka herhangi bir görevden de, aynı istemci üzerinde serbestçe çalışır. Tüketilmemiş yinelenen olaylar birleştiği için, yoğun bir ana iş akışı üç yerine tek bir yeniden getirmeyle sonuçlanabilir. Farklı olaylar birleşmez: çok sayıda URI sayan bir filtre, URI başına bir bekleyen olayı kuyruğa alır.
İzlemeyi bırakmak için bloktan çıkın: unsubscribe diye bir çağrı yoktur. Bloğun sahibi olan görevi iptal etmek bunu sizin yerinize yapar; SDK da listen isteğini aktarımın beklediği biçimde iptal eder: Streamable HTTP üzerinde, o isteğin akışını kapatarak. Uygulamanızın ömrü boyunca çalışan bir izleyici kendiliğinden asla dönmez; bu yüzden kapanışta onu ya da görev grubunun kapsamını iptal edin.
Akışların sona ermesi
Bir akış iki yoldan biriyle sona erer; ikisi de sıradan denetim akışıdır. Sunucunun düzgün bir kapatması async for döngüsünü bitirir; ani bir kopma SubscriptionLost fırlatır.
Aradaki fark tanı amaçlıdır, sonra ne yapılacağıyla ilgili değildir: akış gitmiştir, hiçbir şey yeniden oynatılmamıştır ve hâlâ ilgilenen bir izleyici yeniden dinler ve yeniden getirir.
import anyio
from mcp import Client
from mcp.client.subscriptions import SubscriptionLost
from .tutorial003 import read_board
async def keep_following(client: Client) -> None:
while True:
try:
async with client.listen(resource_subscriptions=["board://sprint"]) as sub:
print(await read_board(client)) # refetch: no replay across streams
async for _event in sub:
print(await read_board(client))
except SubscriptionLost:
pass
# Either ending means the stream is gone. Back off before re-listening:
# a graceful close may be the server shedding load.
await anyio.sleep(1)
Sunucular akışları kendi gerekçeleriyle düzgünce kapatır; birikimi fazla büyüyen bir aboneyi bırakmak da bunlardan biridir. Bu yüzden temiz bir sonlanma, izlemeyi bırakma işareti değildir. Yeniden dinlemeden önce biraz bekleyin.
SubscriptionLost'un yerel bir nedeni de vardır. İstemci en fazla 1024 tüketilmemiş olay tutar; bu kadar geride kalan bir tüketici, sınırsızca büyümek yerine aboneliği kaybeder. async for gövdesini kısa tutun, yavaş işleri başka yerde yapın.
keep_following yalnızca SubscriptionLost'u yakalar. listen()'a girmek ayrıca MCPError (bağlantı başarısız oldu ya da sunucu yöntemi sunmuyor), TimeoutError (onay gelmedi) ve ListenNotSupportedError (2026 öncesi bir bağlantı) da fırlatabilir. İzleyicinizin bunlardan hangilerini yeniden denemesi gerektiğine karar verin: sonuncusu asla düzelmez.
Özet
async with client.listen(...)bloğuna girin; giriş onayı bekler, bu yüzden ondan sonra yayımlanan hiçbir şey kaçmaz.async for event in subile yineleyin. Olaylar yeniden getirmek için birer işarettir, asla yük (payload) değildir.- Aboneliği açın, ardından izleyiciyi bir görev olarak çalıştırın; araç çağrıları onun yanında akmaya devam eder.
- Temiz bir sonlanma döngüyü durdurur; kopma
SubscriptionLostfırlatır. Her iki durumda da: yeniden dinleyin, yeniden getirin, ama önce biraz bekleyin. - Bloktan çıkmak abonelikten çıkmaktır.
Bu olayları yayımlamak, filtreyi daraltmak ve tek bir sürecin ötesine ölçeklemek sunucunun hikâyesidir: Abonelikler. Aynı olaylar istemci tarafındaki bir önbelleği de dürüst tutar; sıradaki sayfa Önbellekleme.