Transportes do 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.
Todo Client conversa com seu servidor por meio de um transporte: aquilo que de fato carrega as mensagens.
Você nunca configura um transporte separadamente. Client recebe um único argumento posicional e deduz o transporte a partir do tipo dele.
O lado do servidor de cada um (o que mcp.run() faz e o que você coloca no deploy) está em Executando seu servidor.
Em memória
Passe o próprio objeto do servidor:
from mcp import Client
from mcp.server import MCPServer
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}."
async def main() -> None:
async with Client(mcp) as client:
result = await client.call_tool("search_books", {"query": "dune"})
print(result.structured_content)
Sem subprocesso, sem porta, sem bytes trafegando na rede. O cliente e o servidor são dois objetos no mesmo processo, e a chamada ainda passa pela camada real do protocolo: search_books é listada, validada e invocada exatamente como seria sobre HTTP.
Isso faz dele duas coisas ao mesmo tempo:
- Uma estrutura de testes. Todo exemplo desta documentação é exercitado dessa forma, e a página Testes constrói o padrão inteiro em torno disso.
- Uma API de embutimento. Uma aplicação que constrói o servidor não precisa de um salto pela rede para chamar as ferramentas dele.
Streamable HTTP
Passe uma string de URL e você tem Streamable HTTP, o transporte atrás do qual você faz o deploy:
from mcp import Client
async def main() -> None:
async with Client("http://localhost:8000/mcp") as client:
result = await client.list_tools()
print([tool.name for tool in result.tools])
Esse é o cliente de produção inteiro. Client envolve a URL em streamable_http_client(...) para você, sobre um httpx2.AsyncClient configurado do jeito que o MCP precisa: follow_redirects=True, um timeout de 30 segundos para connect/write/pool e um timeout de leitura de 300 segundos, porque o servidor pode manter um stream de resposta aberto.
Check
Um Client que você construiu não está conectado. A construção só escolhe o transporte;
é o async with que o abre. Tente usar a conexão antes de entrar e o SDK avisa:
RuntimeError: Client must be used within an async context manager
Nada foi resolvido, buscado ou iniciado quando você escreveu Client("http://..."). Essa linha não custa nada.
Traga seu próprio httpx2.AsyncClient
No momento em que você precisar de um header Authorization, um cookie, um proxy, mTLS ou um timeout diferente, construa o httpx2.AsyncClient você mesmo e entregue-o a streamable_http_client:
import httpx2
from mcp import Client
from mcp.client.streamable_http import streamable_http_client
async def main() -> None:
async with httpx2.AsyncClient(
headers={"Authorization": "Bearer ..."},
timeout=httpx2.Timeout(30.0, read=300.0),
follow_redirects=True,
) as http_client:
transport = streamable_http_client("http://localhost:8000/mcp", http_client=http_client)
async with Client(transport) as client:
result = await client.list_tools()
print([tool.name for tool in result.tools])
Duas coisas para notar:
- Você é o dono do
httpx2.AsyncClient, então é você quem entra e sai dele. O SDK nunca fecha um cliente que não criou. streamable_http_client(url, http_client=...)retorna um transporte, eClient(transport)o aceita como qualquer outra coisa.
Uma observação sobre TLS: httpx2 verifica certificados contra o repositório de confiança do sistema operacional (via
truststore), não contra uma lista de CAs embutida. Em um ambiente
sem um repositório de CAs do sistema utilizável (alguns contêineres mínimos), defina as variáveis de ambiente padrão
SSL_CERT_FILE/SSL_CERT_DIR ou passe um verify=ssl_context explícito ao seu httpx2.AsyncClient
(contexto em
httpx e httpx-sse substituídos por httpx2).
Warning
streamable_http_client costumava aceitar headers= e timeout= diretamente. Não aceita mais:
seus únicos parâmetros são url, http_client e terminate_on_close. Use headers= por
hábito e você recebe:
TypeError: streamable_http_client() got an unexpected keyword argument 'headers'
Tudo que tem cara de HTTP agora vive no único httpx2.AsyncClient que você passa.
Info
httpx2 mantém a API conhecida do httpx, então se você conhece httpx já sabe como fazer auth,
proxies, event hooks, retentativas e limites de conexão aqui. O SDK não acrescenta nada por cima nem
tira nada. É também onde o OAuth se encaixa:
httpx2.AsyncClient(auth=OAuthClientProvider(...)). Esse fluxo inteiro está em Clientes OAuth.
stdio
Um servidor stdio é um subprocesso. O cliente o inicia, escreve JSON-RPC no stdin dele e lê JSON-RPC do stdout dele. É assim que um host de desktop executa um servidor na sua máquina: um host é este código mais uma interface, e Conecte a um host real é a mesma relação vista do lado do host, como um arquivo de configuração.
Descreva o processo com StdioServerParameters, transforme-o em um transporte com stdio_client e entregue isso ao Client:
from mcp import Client, StdioServerParameters
from mcp.client.stdio import stdio_client
server = StdioServerParameters(
command="uv",
args=["run", "server.py"],
env={"BOOKSHOP_API_KEY": "secret"},
)
async def main() -> None:
async with Client(stdio_client(server)) as client:
result = await client.list_tools()
print([tool.name for tool in result.tools])
Client não aceita o objeto de parâmetros sozinho. StdioServerParameters é configuração; stdio_client(server) é o transporte que sabe como iniciar um processo a partir dela. Sempre envolva.
Sair do bloco async with também encerra o subprocesso: fecha o stdin, espera e mata o processo se ele demorar. Você nunca limpa isso por conta própria.
Warning
O processo filho não herda o seu ambiente. Ele recebe uma allow-list mínima (HOME, LOGNAME,
PATH, SHELL, TERM e USER no POSIX), para que nada sensível vaze para um processo que talvez
não tenha sido escrito por você.
Um servidor que precise de uma chave de API não vai encontrá-la ali. Passe-a explicitamente com env=; essas
variáveis são mescladas por cima da allow-list. É isso que BOOKSHOP_API_KEY está fazendo acima.
SSE
sse_client(url), de mcp.client.sse, é o transporte HTTP que o Streamable HTTP substituiu. Envolva-o da mesma forma, Client(sse_client("http://localhost:8000/sse")), para conversar com um servidor que ainda o fala, e não construa nada novo em cima dele.
O protocolo Transport
Para o Client, tudo acima é a mesma coisa.
Um transporte é qualquer gerenciador de contexto assíncrono que produz um par (read, write) de streams de mensagens: formalmente, o protocolo Transport em mcp.client. Client resolve seu argumento pelo tipo: um objeto de servidor conecta no próprio processo, uma str vira streamable_http_client(url) e qualquer outra coisa é aberta diretamente como transporte. É por causa dessa última regra que stdio_client(...), streamable_http_client(...) e sse_client(...) se encaixam todos no mesmo lugar, e que você pode escrever o seu próprio.
Recapitulando
Client(mcp)(o objeto do servidor) conecta em memória. Use para testes e para embutir.Client("http://.../mcp")(uma URL) conecta por Streamable HTTP, o transporte de produção.- Headers, auth, proxies e timeouts pertencem a um
httpx2.AsyncClientque você passa astreamable_http_client(url, http_client=...). Não existe o argumentoheaders=. - stdio é
Client(stdio_client(StdioServerParameters(...))), nunca o objeto de parâmetros sozinho. - O subprocesso recebe um ambiente em allow-list, não o seu;
env=acrescenta a ele. - Um transporte é qualquer coisa com que você possa fazer
async with x as (read, write).Cliententrega direto a esse protocolo tudo que não for um objeto de servidor ou uma URL. - Construir um
Clientescolhe o transporte.async witho abre.
Depois que o transporte está aberto, os dois lados precisam concordar sobre uma versão do protocolo. Normalmente você nunca pensa nisso; quando pensar, Versões do protocolo é a página.