Construindo um cliente e servidor MCP com PHP puro
Integrando a API do Claude ao PostgreSQL utilizando Streamable HTTP.
O Model Context Protocol (MCP) é um protocolo aberto que padroniza a comunicação entre aplicações de inteligência artificial e ferramentas externas. Neste projeto, desenvolvi um cliente e um servidor MCP utilizando PHP puro, integrando a API do Claude a um banco de dados PostgreSQL por meio do transporte Streamable HTTP.
A implementação contempla o processo de inicialização e negociação de versões do protocolo, descoberta e execução de ferramentas, além da comunicação entre cliente e servidor. O servidor disponibiliza duas ferramentas para consulta de usuários no banco de dados, permitindo que o Claude solicite essas operações a partir de interações em linguagem natural.
O que foi desenvolvido?
O projeto possui três componentes principais:
- Aplicação PHP com cliente MCP: responsável pela comunicação com a API do Claude e pela interação com o servidor MCP. Dentro da aplicação, o cliente MCP cuida da comunicação com o servidor pelo protocolo.
- Servidor MCP: responsável por disponibilizar duas ferramentas para consulta de usuários.
- PostgreSQL: banco de dados utilizado como fonte das informações.
Para a comunicação entre cliente e servidor, utilizei o transporte Streamable HTTP.
Entendendo o funcionamento do MCP
Durante a implementação, tive a oportunidade de trabalhar diretamente com algumas das principais etapas do protocolo. A implementação suporta as versões 2025-11-25, 2025-06-18 e 2025-03-26, que utilizam o fluxo de inicialização e negociação de versão descrito a seguir.
- Initialize (
initialize): etapa de inicialização da comunicação, na qual cliente e servidor apresentam informações sobre suas implementações e capacidades. - Version Negotiation: negociação da versão do protocolo, realizada durante a inicialização. Cliente e servidor precisam ser compatíveis com a versão acordada; em seguida, o cliente confirma a inicialização com
notifications/initialized. - Tools List (
tools/list): permite que o cliente descubra quais ferramentas estão disponíveis no servidor MCP, incluindo suas descrições e os parâmetros necessários para utilizá-las. - Tools Call (
tools/call): solicitação de execução de uma ferramenta. O cliente encaminha a chamada ao servidor, que executa a operação e devolve o resultado.
Na revisão 2026-07-28, o protocolo mudou: o handshake com initialize foi removido, e a versão e as capacidades passaram a ser informadas em cada requisição. O projeto apresentado aqui implementa o fluxo das três versões de 2025 mencionadas acima.
Também explorei o Server-Sent Events (SSE), mecanismo de envio de eventos do servidor para o cliente por HTTP. No Streamable HTTP, ele pode ser utilizado para transmitir mensagens em streaming, mas não é obrigatório em toda resposta: o servidor também pode responder com JSON.
Como funciona a integração?
Quando o usuário envia uma pergunta ao Claude, a aplicação encaminha a solicitação à API, disponibilizando as definições das ferramentas descobertas no servidor MCP. Se o modelo identificar que precisa consultar informações no banco de dados, poderá solicitar a execução de uma dessas ferramentas.
Ao receber a resposta da API, a aplicação PHP verifica o campo stop_reason, que indica o motivo da interrupção da resposta. Quando seu valor é tool_use, o Claude está solicitando a execução de uma ferramenta. A aplicação então percorre os blocos do array content e identifica aqueles cujo type é tool_use.
Cada bloco contém o identificador da chamada (id), o nome da ferramenta (name) e seus argumentos (input). Portanto, stop_reason indica por que a resposta foi interrompida, enquanto o bloco tool_use contém os dados da solicitação. Este exemplo simplificado usa um nome de ferramenta ilustrativo:
{
"role": "assistant",
"stop_reason": "tool_use",
"content": [
{
"type": "tool_use",
"id": "toolu_exemplo_01",
"name": "consultar_usuario",
"input": { "usuario_id": 42 }
}
]
}
Com o nome e os argumentos, a aplicação utiliza seu cliente MCP para encaminhar a solicitação ao servidor pelo método tools/call. O servidor consulta o PostgreSQL e devolve o resultado. O Claude solicita a execução, mas é a aplicação PHP que coordena a chamada ao servidor MCP.
A aplicação preserva a resposta do Claude no histórico e envia uma nova mensagem com role: "user", incluindo o resultado em um bloco tool_result. O campo tool_use_id desse bloco recebe o id da chamada original, associando o resultado à solicitação correspondente. O Claude continua o processamento e pode produzir uma resposta em linguagem natural para o usuário.
Nesse fluxo, o Tool Use da API do Claude permite solicitar ferramentas e receber seus resultados. O MCP padroniza a comunicação entre o cliente e o servidor que disponibiliza essas ferramentas. A aplicação PHP conecta os dois mecanismos.
Como o Claude escolhe qual ferramenta utilizar?
O Claude interpreta a intenção do usuário e analisa as definições das ferramentas disponibilizadas pela aplicação para identificar qual atende melhor à solicitação. Essa seleção é semântica: considera o significado da pergunta e o contexto da conversa, em vez de depender apenas de palavras-chave. Por isso, perguntas formuladas de maneiras diferentes podem levar à escolha da mesma ferramenta.
O nome ajuda a identificar a finalidade da ferramenta, mas a descrição é especialmente importante para orientar a escolha. Ela deve explicar, de forma clara e objetiva, o que a ferramenta faz, quando deve ou não ser utilizada, quais informações espera receber e quais resultados retorna. Uma descrição genérica como “Consulta usuários” deixa dúvidas sobre a diferença entre listar cadastros e consultar uma pessoa específica.
Para as duas ferramentas deste projeto, descrições mais específicas poderiam deixar essa diferença explícita:
list_users: “Lista os usuários cadastrados. Utilize quando a solicitação for conhecer os usuários disponíveis, sem selecionar uma pessoa pelo ID. Não utilize para consultar os detalhes de um usuário específico; nesse caso, useget_user. Informe os parâmetros previstos no schema para a listagem; o resultado é a lista de usuários.”get_user: “Consulta as informações de um usuário identificado pelo ID. Utilize quando a solicitação indicar qual usuário deve ser consultado. Recebe o ID e retorna as informações do cadastro correspondente. Não utilize para listar todos os usuários.”
Além da descrição, o input_schema define os parâmetros esperados, seus tipos, descrições e quais são obrigatórios. Isso ajuda o modelo a construir os argumentos da chamada. Para get_user, por exemplo, a definição do parâmetro de ID orienta como identificar o usuário que será consultado.
Na prática, a intenção apresentada na pergunta pode levar a escolhas diferentes:
- “Quais usuários estão cadastrados?” ou “Quero conhecer os usuários do sistema”: a ferramenta adequada é
list_users. - “Mostre as informações do usuário de ID 42”: a ferramenta adequada é
get_user, com o ID informado nos argumentos.
A escolha do Claude continua sendo uma solicitação de execução. A aplicação PHP interpreta o bloco tool_use, encaminha a chamada ao servidor MCP e devolve o resultado ao modelo, conforme o fluxo anterior.
Essa escolha não substitui as validações da aplicação. O servidor deve validar os argumentos recebidos, verificar as permissões de acesso e limitar a execução às operações permitidas. As solicitações do modelo não devem ser aceitas cegamente, mesmo quando o nome da ferramenta e os argumentos parecem corretos.
Por que implementar em PHP puro?
A escolha do PHP puro foi intencional. Queria compreender o funcionamento do protocolo sem depender de abstrações fornecidas por bibliotecas ou frameworks. Foi uma oportunidade de trabalhar diretamente com as mensagens JSON-RPC, entender o processo de negociação de versões, descobrir ferramentas e implementar a comunicação HTTP entre cliente e servidor.
Conclusão
O exercício foi interessante principalmente por permitir compreender o que acontece por trás de uma integração MCP. Mais do que conectar uma aplicação de IA a um banco de dados, pude entender como um protocolo aberto permite padronizar a comunicação entre diferentes clientes, servidores, ferramentas e fontes de dados.
Ainda há muito a explorar no MCP, mas implementar uma solução funcional do zero foi uma excelente maneira de consolidar esses conceitos.