Ana içeriğe geç

İ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:

server.py
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:

client.py
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 with yaş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 bir Client yeniden 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 saran streamable_http_client(url, http_client=...).
  • Bir MCPServer (veya düşük seviyeli Server) ö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çin None (python-sdk sunucuları varsayılan olarak bildirir). Burada server_info.name "Bookshop", server_info.version ise sunucu ne bildiriyorsa odur.
  • client.server_capabilities: sunucunun neler yapabildiği (tools, resources, prompts, completions, ...). Sunucuda olmayan bir yetenek None olur.
  • client.protocol_version: iki tarafın üzerinde anlaştığı protokol sürümü. Burada "2026-07-28".
  • client.instructions: sunucunun instructions= dizesi; sunucu bir tane ayarlamadıysa None.

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

client.py
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.

client.py
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.

client.py
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 bir str URI 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

client.py
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.

client.py
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: bir PromptReference ya da ResourceTemplateReference.
  • 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.

client.py
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, bir StdioServerParameters için alt süreç başlatır, bir aktarıma doğrudan girer ve testlerde sunucu nesnesinin kendisini alır.
  • async with yaşam döngüsünün tamamıdır. İçinde server_capabilities ve protocol_version zaten doludur; sunucu sağladığında server_info ve instructions da öyle.
  • list_tools() size her aracın name, title, description ve input_schema değerlerini verir.
  • call_tool() model için content, kodunuz için structured_content ve is_error döndürür. İstisna fırlatan bir araç istisna değil, sonuçtur.
  • content blok türlerinin bir birleşimidir; okumadan önce isinstance ile daraltın.
  • list_resources / list_resource_templates / read_resource, list_prompts / get_prompt ve complete fiilleri tamamlar.
  • Her list_* cursor= alır; next_cursor None olana kadar döngüye devam edin.

Bir sunucunun istemciden isteyebilecekleri ve bunları nasıl yanıtlayacağınız İstemci callback'leri sayfasında.