O cliente
Tradução automática
Esta página foi traduzida automaticamente a partir da documentação em inglês, e a página em inglês é a versão de referência. Se algo parecer errado, Traduções explica como avisar.
Um Client é como um programa Python conversa com um servidor MCP.
É um objeto com um ciclo de vida: construa, entre no async with, chame os métodos. Cada verbo do protocolo (listar as ferramentas, chamar uma, ler um recurso, renderizar um prompt) é um método async nele que retorna um resultado tipado.
Seu primeiro cliente
from mcp import Client
from mcp.server import MCPServer
mcp = MCPServer("Bookshop", instructions="Search the catalog before recommending a book.")
@mcp.tool()
def search_books(query: str) -> str:
"""Search the catalog by title or author."""
return f"Found 3 books matching {query!r}."
async def main() -> None:
async with Client(mcp) as client:
print(client.server_info)
print(client.server_capabilities)
print(client.protocol_version)
print(client.instructions)
O servidor no topo só está ali para você ter algo a que se conectar. O cliente são as cinco linhas destacadas.
Client(mcp)recebe o próprio objeto servidor. Esse é o transporte em memória: sem subprocesso, sem porta, sem HTTP. É assim que todo exemplo nesta página, e todo teste que você escrever, se conecta.async withé o ciclo de vida. Entrar nele conecta e negocia; sair dele desconecta. Não há um parconnect()/close(), e umClientnão pode ser reutilizado depois que o bloco termina.- Dentro do bloco, os fatos da conexão já estão ali como propriedades comuns.
O que você pode passar para Client
Client recebe um argumento posicional e resolve o transporte a partir do tipo dele:
- Uma instância de
MCPServer(ou doServerde baixo nível): conectada no mesmo processo. - Uma string de URL (
Client("http://localhost:8000/mcp")): Streamable HTTP, o caminho de produção. - Um transporte: qualquer coisa com que você possa fazer
async with ... as (read, write), comostdio_client(...)encapsulando um subprocesso.
Todo o resto desta página é idêntico entre os três. Cabeçalhos, subprocessos, timeouts e o protocolo Transport têm sua própria página: Transportes do cliente.
O que há em um cliente conectado
Quatro propriedades somente leitura, preenchidas no instante em que você entra no bloco:
client.server_info: a identidade do servidor, ouNonepara um servidor da era 2026 que não informa uma (servidores do python-sdk informam por padrão).server_info.nameaqui é"Bookshop",server_info.versioné o que o servidor informar.client.server_capabilities: o que o servidor sabe fazer (tools,resources,prompts,completions, ...). Uma capacidade que o servidor não tem éNone.client.protocol_version: a versão do protocolo em que os dois lados concordaram. Aqui é"2026-07-28".client.instructions: a stringinstructions=do servidor, ouNonese ele não definiu uma.
Você nunca escolheu uma versão do protocolo. Por padrão, o Client sonda o servidor e recorre ao handshake clássico nos mais antigos, então um único cliente funciona contra servidores de qualquer era. Quando você precisar controlar isso, Versões do protocolo tem a história completa.
Tip
client.session é a ClientSession subjacente, a saída de emergência de baixo nível.
Você não vai precisar dela para nada nesta página.
Listando ferramentas
from mcp import Client
from mcp.server import MCPServer
mcp = MCPServer("Bookshop")
@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})."
async def main() -> None:
async with Client(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)
list_tools() retorna um ListToolsResult; as ferramentas estão em .tools. Cada uma é a definição completa que um host entregaria a um modelo:
tool.name # 'search_books'
tool.title # 'Search the catalog'
tool.description # 'Search the catalog by title or author.'
e tool.input_schema é o JSON Schema que o servidor derivou das anotações de tipo da função:
{
"type": "object",
"properties": {
"query": {"title": "Query", "type": "string"},
"limit": {"default": 10, "title": "Limit", "type": "integer"}
},
"required": ["query"],
"title": "search_booksArguments"
}
Esse schema é tudo o que uma UI precisa para renderizar um formulário de argumentos, e tudo o que um modelo precisa para produzir argumentos válidos.
Tip
title é opcional, então uma UI que mostra ferramentas a um humano tem que escolher: o title se houver um,
o name se não. from mcp.shared.metadata_utils import get_display_name faz exatamente isso,
para ferramentas, recursos, templates de recurso e prompts.
Chamando uma ferramenta
call_tool(name, arguments) executa a ferramenta e devolve um CallToolResult.
from pydantic import BaseModel
from mcp import Client
from mcp.server import MCPServer
from mcp.types import TextContent
mcp = MCPServer("Bookshop")
class Book(BaseModel):
title: str
author: str
year: int
@mcp.tool()
def lookup_book(title: str) -> Book:
"""Look up a book by its exact title."""
if title != "Dune":
raise ValueError(f"No book titled {title!r} in the catalog.")
return Book(title="Dune", author="Frank Herbert", year=1965)
async def main() -> None:
async with Client(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)
O lookup_book do servidor retorna um Book do Pydantic. Eis o que o cliente vê:
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
Um valor de retorno, três coisas para ler. Cada uma tem um consumidor diferente.
content: o que o modelo lê
content é uma list de blocos de conteúdo, e um bloco de conteúdo é uma união: TextContent, ImageContent, AudioContent, ResourceLink ou EmbeddedResource. Uma ferramenta pode retornar vários, de tipos diferentes.
É por isso que main faz o narrowing com isinstance(block, TextContent) antes de tocar em block.text. Repare que não há .text fora do isinstance: o verificador de tipos não permite, porque ImageContent tem .data, não .text. A união é honesta sobre o que uma ferramenta pode enviar a você; seu código também deve ser.
structured_content: o que sua aplicação lê
structured_content é o valor de retorno da ferramenta como JSON, correspondendo ao output_schema declarado pela ferramenta. Sem parsing de strings, sem adivinhação.
Quando ambos estão presentes, eles dizem a mesma coisa duas vezes de propósito: content é para um modelo, structured_content é para código. De onde vem a metade estruturada, e como controlá-la, é a página Saída estruturada.
is_error: se a ferramenta falhou
Uma ferramenta que lança uma exceção não lança no seu cliente. Ela volta como um resultado comum com is_error=True.
Check
Peça "Solaris" ao lookup_book (um título que não está no catálogo) e a função lança
ValueError. A chamada ainda retorna normalmente:
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
A mensagem da exceção foi parar em content, onde o modelo pode lê-la e tentar de novo. Isso
é proposital: um erro de ferramenta faz parte da conversa, não é um crash. Sempre olhe is_error
antes de confiar em structured_content.
Warning
is_error=True cobre mais do que o seu próprio raise. Peça uma ferramenta que o servidor nem tem
(call_tool("does_not_exist", {})) e nada lança exceção. Você recebe o mesmo formato de volta,
is_error=True com Unknown tool: does_not_exist em content. Um método de Client lança
MCPError apenas quando o servidor responde com um erro JSON-RPC em vez de um resultado, e
Tratando erros cobre quando um servidor produz cada um.
Recursos
Os verbos de recurso vêm em pares: duas formas de listar, uma forma de ler.
from mcp import Client
from mcp.server import MCPServer
from mcp.types import TextResourceContents
mcp = MCPServer("Bookshop")
@mcp.resource("catalog://genres")
def genres() -> list[str]:
"""The genres the catalog is organised by."""
return ["fiction", "non-fiction", "poetry"]
@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}."
async def main() -> None:
async with Client(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)
list_resources()retorna os recursos concretos, os que têm uma URI fixa. Aqui:['catalog://genres'].list_resource_templates()retorna os parametrizados. Aqui:['catalog://genres/{genre}']. São duas listas diferentes porque um template não pode ser lido até você preenchê-lo.read_resource(uri)recebe uma URIstrcomum e funciona com ambos: passe"catalog://genres/poetry"e o servidor a casa com o template.
read_resource retorna contents, uma lista de TextResourceContents ou BlobResourceContents. Mesma ideia do conteúdo de ferramenta: faça o narrowing com isinstance, depois leia .text (ou .blob).
Um cliente também pode ser avisado quando um recurso muda. Em conexões da era 2025 isso é subscribe_resource(uri) / unsubscribe_resource(uri) - um par de métodos que o MCPServer não implementa, então no protocolo 2026-07-28 (onde esses verbos não existem mais) a requisição responde -32601, Method not found. O substituto de 2026 é um stream subscriptions/listen, que o MCPServer serve sim - server_capabilities.resources.subscribe é True ali - e consumi-lo com client.listen(...) é a página Assinaturas desta seção.
Prompts
from mcp import Client
from mcp.server import MCPServer
mcp = MCPServer("Bookshop")
@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."
async def main() -> None:
async with Client(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)
list_prompts() diz o que o servidor oferece e do que cada prompt precisa:
prompt.name # 'recommend'
prompt.title # 'Recommend a book'
prompt.arguments # [PromptArgument(name='genre', required=True)]
get_prompt(name, arguments) o renderiza. O dict de argumentos é str -> str: argumentos de prompt são sempre strings. O resultado é messages, uma lista de PromptMessage, cada uma com um role e um bloco content:
message.role # 'user'
message.content # TextContent(type='text', text='Recommend one poetry book from the catalog and say why.')
Um host entrega essas mensagens direto ao modelo. A funcionalidade inteira é essa.
Completions
Um servidor com um handler de completion pode autocompletar argumentos de prompts e de templates de recurso enquanto o usuário digita.
from mcp import Client
from mcp.server import MCPServer
from mcp.types import Completion, CompletionArgument, CompletionContext, PromptReference, ResourceTemplateReference
mcp = MCPServer("Bookshop")
GENRES = ["fiction", "non-fiction", "poetry"]
@mcp.prompt()
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)])
async def main() -> None:
async with Client(mcp) as client:
result = await client.complete(
ref=PromptReference(type="ref/prompt", name="recommend"),
argument={"name": "genre", "value": "p"},
)
print(result.completion.values)
refdiz qual prompt ou template você está preenchendo: umaPromptReferenceou umaResourceTemplateReference.argumenté{"name": ..., "value": ...}: o argumento e o que o usuário digitou até agora.
A resposta está em result.completion.values. Digite "p" e o servidor volta com ['poetry']. O lado do servidor, e como um handler usa os outros argumentos já preenchidos para refinar as sugestões, é a página Completions.
Paginação
Todo método list_* aceita um argumento nomeado cursor= e todo resultado carrega um next_cursor. Quando next_cursor é None, você tem tudo.
from mcp import Client
from mcp.server import MCPServer
from mcp.types import Tool
mcp = MCPServer("Bookshop")
@mcp.tool()
def search_books(query: str) -> str:
"""Search the catalog by title or author."""
return f"Found 3 books matching {query!r}."
@mcp.tool()
def reserve_book(title: str) -> str:
"""Put a book on hold."""
return f"Reserved {title!r}."
async def main() -> None:
async with Client(mcp) as client:
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:
break
cursor = page.next_cursor
print([tool.name for tool in tools])
Esse loop está correto contra qualquer servidor. O MCPServer retorna tudo em uma página só, então next_cursor é None e o loop roda uma vez, e é por isso que a maioria do código nunca o escreve. Servidores que paginam de verdade, e as regras que os cursores obedecem, estão em Paginação.
Em testes
Client(mcp), sem processo e sem porta, já é um harness de teste para o seu servidor.
Existe uma flag do construtor feita para isso: Client(mcp, raise_exceptions=True). Ela só tem efeito em conexões em memória, e Testes é a página que a explica e constrói todo o padrão em torno dela.
Recapitulando
Client(x)conecta em memória a um objeto servidor, via Streamable HTTP a uma string de URL, e por qualquer outra coisa via um transporte.async withé o ciclo de vida inteiro. Dentro dele,server_capabilitieseprotocol_versionjá estão preenchidos;server_infoeinstructionstambém, quando o servidor os fornece.list_tools()dá a você oname,title,descriptioneinput_schemade cada ferramenta.call_tool()retornacontentpara o modelo,structured_contentpara o seu código eis_error. Uma ferramenta que lança exceção é um resultado, não uma exceção.contenté uma união de tipos de bloco; faça o narrowing comisinstanceantes de ler.list_resources/list_resource_templates/read_resource,list_prompts/get_promptecompletecompletam os verbos.- Todo
list_*aceitacursor=; itere aténext_cursorserNone.
As coisas que um servidor pode pedir ao cliente, e como você as responde, são os Callbacks do cliente.