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
Um cliente precisa de um servidor com quem conversar. Esta Bookshop é o servidor a que todo trecho desta página se conecta. Salve-o como server.py e deixe-o rodando via HTTP:
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
Isso o serve em http://localhost:8000/mcp. O cliente é um programa à parte. Salve-o como client.py e execute python client.py em um segundo terminal:
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")recebe uma URL, então se conecta via Streamable HTTP ao servidor que você acabou de iniciar.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 string de URL (
Client("http://localhost:8000/mcp")): Streamable HTTP, o transporte atrás do qual você faz o deploy. - Um
StdioServerParameters: o comando a iniciar como subprocesso local, com o qual se conversa pelo stdin e stdout dele. - Um transporte: qualquer coisa com que você possa fazer
async with ... as (read, write), comostreamable_http_client(url, http_client=...)em volta do seu próprio cliente HTTP. - Uma instância de
MCPServer(ou doServerde baixo nível): conectada no mesmo processo, sem subprocesso e sem porta. Essa é para testes, e Testes se apoia nela.
Todo o resto desta página é idêntico entre os quatro. 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
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() retorna um ListToolsResult; as ferramentas estão em .tools. Cada uma é a definição completa que um host entregaria a um modelo. Eis a primeira:
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.
A segunda ferramenta, lookup_book, foi registrada sem um title=, então o tool.title dela é None.
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.
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)
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
ToolError. 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 do ToolError 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. (Se a ferramenta tivesse
quebrado com alguma outra exceção, content diria apenas Error executing tool lookup_book.) 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.
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()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
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() 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.
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)
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.
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 está correta 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
Todo client.py desta página alcançou o server.py via HTTP. Em um teste você pula a rede e entrega ao Client o próprio objeto servidor: from server import mcp, depois Client(mcp). Sem processo, sem porta, e todo método acima funciona igual.
Existe uma flag do construtor feita para isso: Client(mcp, raise_exceptions=True). Ela só tem efeito em conexões no mesmo processo, e Testes é a página que a explica e constrói todo o padrão em torno dela.
Recapitulando
Client(x)conecta via Streamable HTTP a uma string de URL, inicia um subprocesso para umStdioServerParameters, entra direto em um transporte e, em testes, recebe o próprio objeto servidor.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.