İstemci
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.
Client, bir Python programının bir MCP sunucusuyla konuşmasını sağlayan nesnedir.
Tek bir yaşam döngüsü olan tek bir nesnedir: oluşturun, async with bloğuna girin, yöntemleri çağırın. Her protokol fiili (araçları listeleme, birini çağırma, bir kaynağı okuma, bir prompt'u oluşturma) bu nesne üzerinde, türü belirli bir sonuç döndüren bir async yöntemdir.
İlk istemciniz
Bir istemcinin konuşacağı bir sunucuya ihtiyacı vardır. Bu sayfadaki her örneğin bağlandığı sunucu aşağıdaki Bookshop. Onu server.py olarak kaydedin ve HTTP üzerinden çalışır durumda bırakın:
from pydantic import BaseModel
from mcp.server import MCPServer
from mcp.server.mcpserver.exceptions import ToolError
from mcp.types import Completion, CompletionArgument, CompletionContext, PromptReference, ResourceTemplateReference
mcp = MCPServer("Bookshop", instructions="Search the catalog before recommending a book.")
GENRES = ["fiction", "non-fiction", "poetry"]
class Book(BaseModel):
title: str
author: str
year: int
@mcp.tool(title="Search the catalog")
def search_books(query: str, limit: int = 10) -> str:
"""Search the catalog by title or author."""
return f"Found 3 books matching {query!r} (showing up to {limit})."
@mcp.tool()
def lookup_book(title: str) -> Book:
"""Look up a book by its exact title."""
if title != "Dune":
raise ToolError(f"No book titled {title!r} in the catalog.")
return Book(title="Dune", author="Frank Herbert", year=1965)
@mcp.resource("catalog://genres")
def genres() -> list[str]:
"""The genres the catalog is organised by."""
return GENRES
@mcp.resource("catalog://genres/{genre}")
def books_in_genre(genre: str) -> str:
"""Every title we stock in one genre."""
return f"3 books filed under {genre}."
@mcp.prompt(title="Recommend a book")
def recommend(genre: str) -> str:
"""Ask for a recommendation in a genre."""
return f"Recommend one {genre} book from the catalog and say why."
@mcp.completion()
async def complete_genre(
ref: PromptReference | ResourceTemplateReference,
argument: CompletionArgument,
context: CompletionContext | None,
) -> Completion | None:
return Completion(values=[genre for genre in GENRES if genre.startswith(argument.value)])
uv run mcp run server.py --transport streamable-http
Bu, sunucuyu http://localhost:8000/mcp adresinde sunar. İstemci ayrı bir programdır. Onu client.py olarak kaydedin ve ikinci bir terminalde python client.py komutunu çalıştırın:
import anyio
from mcp import Client
async def main() -> None:
async with Client("http://localhost:8000/mcp") as client:
print(client.server_info)
print(client.server_capabilities)
print(client.protocol_version)
print(client.instructions)
if __name__ == "__main__":
anyio.run(main)
Client("http://localhost:8000/mcp")çağrısına bir URL verilir; bu yüzden az önce başlattığınız sunucuya Streamable HTTP üzerinden bağlanır.async withyaşam döngüsüdür. Bloğa girdiğinizde bağlantı kurulur ve anlaşma yapılır; çıktığınızda bağlantı kesilir.connect()/close()çifti yoktur ve blok bittikten sonra birClientyeniden kullanılamaz.- Bloğun içinde bağlantı bilgileri düz özellikler olarak zaten hazırdır.
Client'a geçirebilecekleriniz
Client tek bir konumsal argüman alır ve aktarımı onun türünden çözümler:
- Bir URL dizesi (
Client("http://localhost:8000/mcp")): Streamable HTTP, dağıtımda kullandığınız aktarım. - Bir
StdioServerParameters: yerel bir alt süreç olarak başlatılacak komut; onunla stdin ve stdout'u üzerinden konuşulur. - Bir aktarım:
async with ... as (read, write)ile kullanabileceğiniz herhangi bir şey; örneğin kendi HTTP istemcinizi saranstreamable_http_client(url, http_client=...). - Bir
MCPServer(veya düşük seviyeliServer) örneği: süreç içinde bağlanır; alt süreç yok, port yok. Bu seçenek testler içindir ve Test etme sayfası onun üzerine kurulur.
Bu sayfadaki geri kalan her şey dördünde de aynıdır. Başlıklar, alt süreçler, zaman aşımları ve Transport protokolünün kendi sayfası var: İstemci aktarımları.
Bağlı bir istemcide bulunanlar
Bloğa girdiğiniz anda doldurulan dört salt okunur özellik:
client.server_info: sunucunun kimliği; kimlik bildirmeyen 2026 neslinden bir sunucu içinNone(python-sdk sunucuları varsayılan olarak bildirir). Buradaserver_info.name"Bookshop",server_info.versionise sunucu ne bildiriyorsa odur.client.server_capabilities: sunucunun neler yapabildiği (tools,resources,prompts,completions, ...). Sunucuda olmayan bir yetenekNoneolur.client.protocol_version: iki tarafın üzerinde anlaştığı protokol sürümü. Burada"2026-07-28".client.instructions: sunucununinstructions=dizesi; sunucu bir tane ayarlamadıysaNone.
Hiç protokol sürümü seçmediniz. Varsayılan olarak Client sunucuyu yoklar ve eski sunucularda klasik el sıkışmaya geri döner; böylece tek bir istemci her nesilden sunucuyla çalışır. Bunu denetlemeniz gerektiğinde ayrıntıların tamamı Protokol sürümleri sayfasında.
Tip
client.session, alttaki ClientSession'dır; düşük seviyeli kaçış kapısı.
Bu sayfadaki hiçbir şey için ona ihtiyacınız olmaz.
Araçları listeleme
import anyio
from mcp import Client
async def main() -> None:
async with Client("http://localhost:8000/mcp") as client:
result = await client.list_tools()
for tool in result.tools:
print(tool.name)
print(tool.title)
print(tool.description)
print(tool.input_schema)
if __name__ == "__main__":
anyio.run(main)
list_tools() bir ListToolsResult döndürür; araçlar .tools içindedir. Her biri, bir host'un modele vereceği eksiksiz tanımdır. İşte ilki:
tool.name # 'search_books'
tool.title # 'Search the catalog'
tool.description # 'Search the catalog by title or author.'
tool.input_schema ise sunucunun fonksiyonun tür ipuçlarından türettiği JSON Schema'dır:
{
"type": "object",
"properties": {
"query": {"title": "Query", "type": "string"},
"limit": {"default": 10, "title": "Limit", "type": "integer"}
},
"required": ["query"],
"title": "search_booksArguments"
}
Bu şema, bir arayüzün argüman formu oluşturması için gereken her şeydir; bir modelin geçerli argümanlar üretmesi için gereken her şey de odur.
İkinci araç olan lookup_book, title= olmadan kaydedildi; bu yüzden tool.title değeri None.
Tip
title isteğe bağlıdır; bu yüzden araçları bir insana gösteren arayüzün seçim yapması gerekir: varsa title,
yoksa name. from mcp.shared.metadata_utils import get_display_name tam olarak bunu yapar;
araçlar, kaynaklar, kaynak şablonları ve prompt'lar için.
Bir aracı çağırma
call_tool(name, arguments) aracı çalıştırır ve size bir CallToolResult geri verir.
import anyio
from mcp import Client
from mcp.types import TextContent
async def main() -> None:
async with Client("http://localhost:8000/mcp") as client:
result = await client.call_tool("lookup_book", {"title": "Dune"})
for block in result.content:
if isinstance(block, TextContent):
print(block.text)
print(result.structured_content)
print(result.is_error)
if __name__ == "__main__":
anyio.run(main)
Sunucunun lookup_book aracı bir Pydantic Book döndürür. İstemcinin gördüğü şudur:
result.content # [TextContent(type='text', text='{\n "title": "Dune",\n "author": "Frank Herbert",\n "year": 1965\n}')]
result.structured_content # {'title': 'Dune', 'author': 'Frank Herbert', 'year': 1965}
result.is_error # False
Tek dönüş değeri, okunacak üç şey. Her birinin tüketicisi farklı.
content: modelin okuduğu
content, içerik bloklarından oluşan bir list'tir ve bir içerik bloğu bir birleşim (union) türüdür: TextContent, ImageContent, AudioContent, ResourceLink veya EmbeddedResource. Bir araç farklı türlerden birkaç tane döndürebilir.
main'in block.text'e dokunmadan önce isinstance(block, TextContent) ile türü daraltmasının nedeni budur. isinstance dışında hiç .text olmadığına dikkat edin: tür denetleyicisi buna izin vermez, çünkü ImageContent'te .text değil .data vardır. Birleşim türü, bir aracın size ne gönderebileceği konusunda dürüsttür; kodunuz da öyle olmalı.
structured_content: uygulamanızın okuduğu
structured_content, aracın JSON olarak dönüş değeridir ve aracın bildirdiği output_schema ile eşleşir. Dize ayrıştırma yok, tahmin yürütme yok.
İkisi de varsa aynı şeyi bilerek iki kez söylerler: content model için, structured_content kod içindir. Yapılandırılmış yarının nereden geldiği ve nasıl denetleneceği Yapılandırılmış çıktı sayfasında.
is_error: aracın başarısız olup olmadığı
İstisna fırlatan bir araç, istemcinizde istisna fırlatmaz. is_error=True taşıyan sıradan bir sonuç olarak geri döner.
Check
lookup_book'tan "Solaris"'i isteyin (katalogda olmayan bir başlık); fonksiyon
ToolError fırlatır. Çağrı yine de normal biçimde döner:
result.is_error # True
result.content # [TextContent(type='text', text="Error executing tool lookup_book: No book titled 'Solaris' in the catalog.")]
result.structured_content # None
ToolError'ın mesajı content'e düştü; model onu orada okuyup yeniden deneyebilir. Bu
kasıtlıdır: bir araç hatası çökme değil, konuşmanın bir parçasıdır. (Araç başka bir istisnayla
çökmüş olsaydı content'te yalnızca Error executing tool lookup_book yazardı.)
structured_content'e güvenmeden önce her zaman is_error'a bakın.
Warning
is_error=True, kendi raise'inizden fazlasını kapsar. Sunucuda hiç olmayan bir araç isteyin
(call_tool("does_not_exist", {})); hiçbir şey fırlatılmaz. Aynı şekil geri gelir:
content'te Unknown tool: does_not_exist ile birlikte is_error=True. Bir Client yöntemi
yalnızca sunucu sonuç yerine bir JSON-RPC hatası ile yanıt verdiğinde MCPError fırlatır;
sunucunun hangisini ne zaman ürettiği Hataları ele alma sayfasında.
Kaynaklar
Kaynak fiilleri çift gelir: listelemenin iki yolu, okumanın tek yolu.
import anyio
from mcp import Client
from mcp.types import TextResourceContents
async def main() -> None:
async with Client("http://localhost:8000/mcp") as client:
listed = await client.list_resources()
print([resource.uri for resource in listed.resources])
templates = await client.list_resource_templates()
print([template.uri_template for template in templates.resource_templates])
result = await client.read_resource("catalog://genres/poetry")
for contents in result.contents:
if isinstance(contents, TextResourceContents):
print(contents.text)
if __name__ == "__main__":
anyio.run(main)
list_resources()somut kaynakları, yani sabit URI'si olanları döndürür. Burada:['catalog://genres'].list_resource_templates()parametreli olanları döndürür. Burada:['catalog://genres/{genre}']. İki ayrı liste olmalarının nedeni, bir şablonun siz onu doldurana kadar okunabilir olmamasıdır.read_resource(uri)düz birstrURI alır ve ikisinde de çalışır:"catalog://genres/poetry"geçirin, sunucu onu şablonla eşleştirir.
read_resource, TextResourceContents veya BlobResourceContents öğelerinden oluşan bir liste olan contents döndürür. Araç içeriğiyle aynı fikir: isinstance ile daraltın, sonra .text'i (veya .blob'u) okuyun.
Bir istemciye bir kaynağın ne zaman değiştiği de bildirilebilir. 2025 neslinden bağlantılarda bu, subscribe_resource(uri) / unsubscribe_resource(uri) çiftidir; MCPServer'ın uygulamadığı bir yöntem çifti olduğundan, 2026-07-28 sürümündeki bağlantıda (bu fiillerin artık var olmadığı yerde) istek -32601, Method not found ile yanıtlanır. 2026'daki karşılığı, MCPServer'ın gerçekten sunduğu bir subscriptions/listen akışıdır (orada server_capabilities.resources.subscribe değeri True'dur) ve onu client.listen(...) ile tüketmek bu bölümün Abonelikler sayfasının konusudur.
Prompt'lar
import anyio
from mcp import Client
async def main() -> None:
async with Client("http://localhost:8000/mcp") as client:
listed = await client.list_prompts()
print(listed.prompts)
result = await client.get_prompt("recommend", {"genre": "poetry"})
for message in result.messages:
print(message.role, message.content)
if __name__ == "__main__":
anyio.run(main)
list_prompts() size sunucunun neler sunduğunu ve her prompt'un neye ihtiyaç duyduğunu söyler:
prompt.name # 'recommend'
prompt.title # 'Recommend a book'
prompt.arguments # [PromptArgument(name='genre', required=True)]
get_prompt(name, arguments) onu oluşturur. Argümanlar sözlüğü str -> str biçimindedir: prompt argümanları her zaman dizedir. Sonuç messages'dır; her biri bir role ve bir content bloğu taşıyan PromptMessage öğelerinden oluşan bir liste:
message.role # 'user'
message.content # TextContent(type='text', text='Recommend one poetry book from the catalog and say why.')
Host bu mesajları doğrudan modele verir. Özelliğin tamamı bu.
Tamamlamalar
Tamamlama işleyicisi olan bir sunucu, kullanıcı yazdıkça prompt ve kaynak şablonu argümanlarını otomatik tamamlayabilir.
import anyio
from mcp import Client
from mcp.types import PromptReference
async def main() -> None:
async with Client("http://localhost:8000/mcp") as client:
result = await client.complete(
ref=PromptReference(type="ref/prompt", name="recommend"),
argument={"name": "genre", "value": "p"},
)
print(result.completion.values)
if __name__ == "__main__":
anyio.run(main)
ref, hangi prompt'u veya şablonu doldurduğunuzu söyler: birPromptReferenceya daResourceTemplateReference.argument,{"name": ..., "value": ...}biçimindedir: argüman ve kullanıcının şimdiye kadar yazdığı.
Yanıt result.completion.values içindedir. "p" yazın, sunucu ['poetry'] ile döner. Sunucu tarafı ve bir işleyicinin önerilerini daraltmak için önceden doldurulmuş diğer argümanları nasıl kullandığı Tamamlamalar sayfasında.
Sayfalama
Her list_* yöntemi bir cursor= anahtar sözcüğü alır ve her sonuç bir next_cursor taşır. next_cursor None olduğunda her şeyi almışsınız demektir.
import anyio
from mcp import Client
from mcp.types import Tool
async def list_all_tools(client: Client) -> list[Tool]:
tools: list[Tool] = []
cursor: str | None = None
while True:
page = await client.list_tools(cursor=cursor)
tools.extend(page.tools)
if page.next_cursor is None:
return tools
cursor = page.next_cursor
async def main() -> None:
async with Client("http://localhost:8000/mcp") as client:
tools = await list_all_tools(client)
print([tool.name for tool in tools])
if __name__ == "__main__":
anyio.run(main)
list_all_tools her sunucuya karşı doğrudur. MCPServer her şeyi tek sayfada döndürür; bu yüzden next_cursor None olur ve döngü bir kez çalışır. Çoğu kodun bunu hiç yazmamasının nedeni budur. Gerçekten sayfalayan sunucular ve imleçlerin uyduğu kurallar Sayfalama sayfasında.
Testlerde
Bu sayfadaki her client.py, server.py dosyasına HTTP üzerinden ulaştı. Bir testte ağı atlar ve Client'a sunucu nesnesinin kendisini verirsiniz: from server import mcp, ardından Client(mcp). Süreç yok, port yok; yukarıdaki her yöntem aynı şekilde çalışır.
Bunun için yapılmış tek bir kurucu bayrağı var: Client(mcp, raise_exceptions=True). Yalnızca süreç içi bağlantılarda etkisi olur; onu açıklayan ve bütün kalıbı onun etrafında kuran sayfa ise Test etme.
Özet
Client(x)bir URL dizesine Streamable HTTP üzerinden bağlanır, birStdioServerParametersiçin alt süreç başlatır, bir aktarıma doğrudan girer ve testlerde sunucu nesnesinin kendisini alır.async withyaşam döngüsünün tamamıdır. İçindeserver_capabilitiesveprotocol_versionzaten doludur; sunucu sağladığındaserver_infoveinstructionsda öyle.list_tools()size her aracınname,title,descriptionveinput_schemadeğerlerini verir.call_tool()model içincontent, kodunuz içinstructured_contentveis_errordöndürür. İstisna fırlatan bir araç istisna değil, sonuçtur.contentblok türlerinin bir birleşimidir; okumadan önceisinstanceile daraltın.list_resources/list_resource_templates/read_resource,list_prompts/get_promptvecompletefiilleri tamamlar.- Her
list_*cursor=alır;next_cursorNoneolana kadar döngüye devam edin.
Bir sunucunun istemciden isteyebilecekleri ve bunları nasıl yanıtlayacağınız İstemci callback'leri sayfasında.