A maior parte da orientação sobre o Model Context Protocol assume que está a construir um servidor à frente de uma loja ou de uma base de dados, algo com encomendas e inventário. Nós fizemos o menos óbvio: transformámos um site de marketing estático num servidor MCP ativo e só de leitura. Qualquer cliente MCP pode agora fazer POST para um único endpoint e perguntar que serviços existem ou como iniciar um pedido, em JSON-RPC tipado, sem analisar uma única página de HTML.
Este é um relato prático dessa construção. É deliberadamente estreito: duas ferramentas só de leitura, sem base de dados, sem escritas. Essa estreiteza é o ponto central, e a maioria das decisões interessantes decorre dela. O que se segue é porque é que um site de conteúdos beneficia sequer de um endpoint MCP, porque é que o endpoint é uma Cloudflare Pages Function em vez de uma rota de framework, porque é que fizemos JSON-RPC à mão em vez de puxar o SDK, e o único bug da barra final que quebra silenciosamente todos os POST se o deixar passar.
Porquê colocar um servidor MCP num site de marketing
Uma loja expõe MCP porque um agente precisa de executar uma ação: verificar stock, montar um carrinho, fazer uma encomenda. Um site de marketing não tem carrinho. Então para que serve a superfície de ferramentas?
A resposta honesta é a descoberta por agentes. O nosso site já corria uma camada de descoberta por agentes: um llms.txt, um conjunto de documentos .well-known e um cartão de servidor MCP em /.well-known/mcp/server-card.json. Esse cartão descrevia um servidor, o seu transporte e as suas capacidades. Chegava a declarar uma capacidade tools. Havia um problema: nada existia por trás dele. O cartão anunciava um servidor que não existia. Um agente que seguisse o cartão e tentasse ligar-se não obtinha nada.
Esse é um estado comum para estes ficheiros. São gerados, são anunciados num cabeçalho Link e depois apontam para um recurso que nunca foi construído. O cartão é uma promessa sem implementação.
Por isso o objetivo não era inventar uma superfície nova. Era tornar real uma superfície existente e já anunciada. As ferramentas decorrem diretamente daquilo para que serve um site de marketing:
check_servicesdevolve o catálogo de serviços: id, nome, descrição, categoria e URL canónico.request_quotedevolve o URL de contacto localizado e as instruções para submeter um pedido.
Duas ferramentas, ambas só de leitura. Um agente pode agora enumerar o que fazemos e dar a um utilizador o passo seguinte correto, de forma determinística, em vez de adivinhar a partir de texto corrido.
A decisão de arquitetura: uma Pages Function, não uma rota Astro
O site é construído com Astro em modo de saída estática. Não há adaptador SSR. Todas as páginas são pré-renderizadas para HTML em tempo de build, e os ficheiros no estilo de API que serve (o JSON do catálogo de serviços, o perfil do agente) são ficheiros estáticos em public/, não rotas de servidor.
Esse único facto conduz todo o desenho. Um endpoint dinâmico que lê o corpo de um pedido e devolve uma resposta calculada não pode ser uma rota Astro aqui, porque não há servidor para o correr. Adicionar um significaria mudar o modo de saída para híbrido ou servidor e anexar um adaptador, o que é uma alteração grande e arriscada a uma build que já produz milhares de páginas.
O site já corre, no entanto, Cloudflare Pages Functions escritas à mão: um middleware que trata redirecionamentos e alguns pequenos handlers de API. É essa a costura. Um endpoint MCP dinâmico é apenas mais uma Pages Function, functions/mcp.ts, ao lado das outras. Faz deploy com a build normal, não precisa de adaptador e não muda nada quanto à forma como as páginas são renderizadas.
A lição generaliza-se. Quando quer adicionar um endpoint dinâmico a um site que de resto é estático, não recorra ao modo de servidor da framework. Recorra ao primitivo de edge function que o seu host já lhe oferece. No Cloudflare Pages isso é uma Function. O custo é um ficheiro, não uma reconstrução do seu modelo de renderização.
JSON-RPC feito à mão ganha ao SDK para duas ferramentas
O reflexo quando lê “servidor MCP” é instalar @modelcontextprotocol/sdk. Para um servidor stdio com muitas ferramentas, recursos e prompts, esse reflexo está correto. Para duas ferramentas só de leitura numa edge function, não está.
O protocolo de comunicação do MCP é JSON-RPC 2.0. Um servidor que responde a um cliente MCP precisa de tratar um pequeno conjunto de métodos:
initialize, em que o servidor devolve a sua versão do protocolo, as capacidades e a identidade.tools/list, em que devolve as definições das ferramentas com o respetivo JSON Schema.tools/call, em que executa uma ferramenta nomeada e devolve o resultado embrulhado em conteúdo MCP.- notificações como
notifications/initialized, que não transportam id e não esperam resposta.
Essa é a superfície inteira para um servidor só de leitura. Implementá-la à mão são aproximadamente 120 linhas. O dispatch é um switch simples:
switch (method) {
case "initialize":
return ok({ protocolVersion, capabilities: { tools: { listChanged: false } }, serverInfo });
case "tools/list":
return ok({ tools: TOOLS });
case "tools/call":
return ok({ content: [{ type: "text", text: callTool(name, args, services).text }] });
default:
return err(-32601, `Method not found: ${method}`);
}
Pese isso contra o SDK. O SDK foi concebido para stdio e para o protocolo completo. Num runtime de edge herda as suas suposições de bundling e a sua maquinaria de transporte para funcionalidades que não está a usar. Para duas ferramentas de leitura, uma dependência sobre a qual tem de raciocinar é uma troca pior do que 120 linhas que controla por completo. É a mesma contenção só-de-leitura-primeiro aplicada às dependências: exponha o mínimo, seja dono do mínimo.
A única coisa que vale a pena fazer com cuidado à mão é o contrato de erros. O JSON-RPC tem códigos de erro definidos, e os clientes MCP esperam-nos: -32700 para um erro de parsing, -32601 para um método desconhecido, -32602 para parâmetros inválidos. Acertar nisso é o que faz um servidor feito à mão comportar-se como um a sério perante um cliente estrito.
Só de leitura primeiro é uma decisão de segurança, não uma limitação
A tentadora terceira ferramenta é uma que capta um lead: recebe um nome, um email e uma mensagem, e envia-nos por email. Não construa isso como uma ferramenta MCP aberta.
Um endpoint público com uma ferramenta de escrita é um depósito de spam. Qualquer pessoa na internet pode chamar tools/call com submit_quote e uma carga falsa, e agora a sua caixa de entrada, o seu CRM ou a sua base de dados é um alvo sem atrito e sem ninguém no circuito. No momento em que uma ferramenta escreve, o endpoint precisa de autenticação, limitação de taxa e tratamento de abusos, o que é uma superfície grande para um site de marketing defender.
Por isso request_quote não escreve nada. Devolve o URL de contacto localizado e orientação estruturada:
if (name === "request_quote") {
const lang = LOCALES.includes(String(args?.lang)) ? String(args.lang) : "en";
return { text: JSON.stringify({
contact_url: CONTACT_URLS[lang],
method: "web-form",
note: "Read-only endpoint. Submit the inquiry through the contact form; this tool does not send it for you.",
reply_time: "within one working day",
}, null, 2) };
}
O agente obtém tudo o que precisa para fazer o utilizador avançar: a página de contacto certa para o idioma do utilizador, e uma indicação clara de que a submissão acontece através do formulário. A pessoa mantém-se no circuito. O endpoint não tem nada de que se possa abusar.
Isto não é um desenho mais fraco imposto pela cautela. É a mesma postura que recomendamos aos clientes que constroem MCP para os seus próprios sistemas: só de leitura primeiro, adicione escritas apenas por trás de autenticação quando houver uma razão concreta. Um servidor só de leitura é honesto sobre o que é, e é seguro deixá-lo aberto.
| Escolha de desenho | Ferramenta aberta de leitura-escrita | Encaminhamento só de leitura |
|---|---|---|
| Autenticação necessária | Sim | Não |
| Superfície de spam | Alta | Nenhuma |
| Pessoa no circuito | Opcional | Sempre |
| Código a defender | Limitação de taxa, validação, tratamento de abusos | Nenhum |
| Adequação a um site de marketing público | Fraca | Boa |
A armadilha da barra final que perde o corpo do seu POST
Esta custou tempo real de depuração, por isso vale a pena dizê-la sem rodeios.
O nosso site canonicaliza os URL para uma barra final. O middleware redireciona com 301 qualquer caminho que não a tenha para a versão com barra. Isso está bem para páginas. É um desastre silencioso para um endpoint JSON-RPC.
Um POST /mcp bate no redirecionamento e devolve 301 para /mcp/. Muitos clientes HTTP, ao seguir o redirecionamento, não reenviam o corpo do POST, ou baixam o método. O cliente MCP vê uma resposta vazia ou falhada e conclui que o servidor está partido. O servidor está bem. O pedido nunca chegou com o seu corpo.
A correção não é lutar contra o middleware. É anunciar o URL que o site serve de facto. O cartão do servidor MCP e todas as referências de descoberta apontam para /mcp/, com a barra final, para que um cliente bem comportado faça POST diretamente para o URL canónico e nunca toque no redirecionamento. Se a sua framework ou host normaliza barras, decida qual a forma canónica e anuncie exatamente essa forma em todo o lado.
Pode verificar o endpoint ativo da mesma forma que nós:
curl -s -X POST https://wppoland.com/mcp/ \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
Um POST sem barra para o mesmo host mostrar-lhe-á o 301 em vez disso.
Como isto encaixa na prontidão para agentes e no GEO
A generative engine optimization é sobretudo ser legível e verificável para modelos. Dados estruturados, entidades claras, afirmações consistentes entre as suas próprias superfícies e as externas. Um endpoint MCP ativo é uma versão forte dessa legibilidade: não é uma pista sobre o seu conteúdo, é uma interface invocável para ele.
Duas coisas tornam-no digno do pequeno esforço mesmo com a adoção ainda numa fase inicial. Primeiro, fecha a distância entre o que a camada de descoberta anuncia e o que existe. Um cartão que aponta para um servidor a funcionar é um sinal coerente; um cartão que aponta para nada é um sinal partido, e sinais partidos são piores do que sinais ausentes. Segundo, para quem vende esta capacidade, o endpoint é prova. Construímos servidores MCP para clientes, e a demonstração mais credível disso é um público, só de leitura, a correr no nosso próprio domínio, ao lado do servidor MCP para WooCommerce de código aberto que publicamos.
Nenhum grande fornecedor de IA se compromete formalmente hoje a ler cartões de servidor MCP ou llms.txt. É uma ressalva justa e afirmamo-la sem rodeios. O endpoint é barato de correr, torna verdadeira uma promessa anunciada, e é um artefacto a funcionar em vez de uma alegação. Esses três juntos ultrapassam o limiar.
O que deixámos deliberadamente de fora
Uma lista curta, porque saber o que uma construção salta é tão útil como saber o que inclui.
- Ainda sem ferramenta
get_case_studies. Um agente pode ler os casos de estudo a partir dollms.txte das páginas ligadas. Adicionamos a ferramenta quando um cliente real a pedir, não antes. - Sem capacidade
resourcesouprompts. O servidor declara apenastools, porque é tudo o que implementa. Declarar uma capacidade que não serve é pior do que omiti-la: um cliente que chameresources/listrecebe um erro em vez de um limpo “não suportado”. - Sem SDK, como já abordado acima.
- Sem escritas, como já abordado acima.
Cada omissão é uma decisão, não um descuido. O endpoint faz exatamente o que as duas intenções exigem e nada mais, e é por isso que é pequeno o suficiente para se raciocinar sobre ele numa sentada e seguro o suficiente para se deixar aberto à internet.
Para onde ir a seguir
Se quiser a versão mais completa destas decisões, o cluster à volta deste artigo cobre o terreno adjacente: construir um servidor MCP para WooCommerce para o caso com estado e voltado para a loja, MCP vs REST: quando cada um vence para saber se precisa sequer de MCP, e padrões de autenticação MCP para o momento em que adiciona uma ferramenta de escrita e precisa de autenticação. A versão comercial deste trabalho vive na página de serviço desenvolvimento de servidores MCP.
A versão curta cabe numa frase: um site de marketing pode ser um servidor MCP, o endpoint é uma edge function, mantenha todas as ferramentas só de leitura, e anuncie o URL que o seu host serve de facto.







