Servidor MCP para Seu CMS: Deixe Agentes de IA Ler e Escrever Conteúdo
Passei os últimos três meses construindo servidores MCP para projetos de clientes, e agora estou convencido de que é assim que todo CMS será acessado dentro de dois anos. Não através de dashboards. Não através de APIs REST que humanos chamam manualmente. Através de agentes de IA que entendem seu modelo de conteúdo e podem ler, criar e atualizar conteúdo em seu lugar.
O Model Context Protocol (MCP) é um padrão aberto que a Anthropic lançou no final de 2024. Ele fornece aos modelos de IA uma maneira estruturada de interagir com ferramentas externas e fontes de dados. Pense nele como um adaptador universal entre um LLM e... qualquer coisa. Seu banco de dados. Seu sistema de arquivos. Seu CMS.
Este artigo percorre a construção de servidores MCP para três backends de CMS populares: Payload CMS, Supabase (usado como um CMS headless) e WordPress. Cobriremos a arquitetura, o código real e as pegadinhas que encontrei pelo caminho.
Sumário
- O que é MCP e Por Que Você Deveria Se Importar
- Arquitetura MCP para Integração com CMS
- Construindo um Servidor MCP para Payload CMS
- Construindo um Servidor MCP para Supabase
- Construindo um Servidor MCP para WordPress
- Comparando as Três Abordagens
- Considerações de Segurança
- Casos de Uso do Mundo Real
- Perguntas Frequentes
O que é MCP e Por Que Você Deveria Se Importar
O Model Context Protocol é essencialmente um protocolo baseado em JSON-RPC 2.0 que define como os modelos de IA se comunicam com sistemas externos. Antes do MCP, se você quisesse que Claude ou GPT interagisse com seu CMS, você construiria uma integração de chamada de função personalizada para cada modelo. Esquemas diferentes, fluxos de autenticação diferentes, tudo diferente.
O MCP padroniza isso. Você constrói um servidor, e qualquer cliente compatível com MCP (Claude Desktop, Cursor, Cline, agentes personalizados) pode se conectar a ele.
Aqui está o que torna isso interessante especificamente para trabalho com CMS:
- Equipes de conteúdo podem pedir a uma IA para "encontrar todos os posts de blog publicados no mês passado que mencionem React" e obter resultados reais de seu CMS real
- Desenvolvedores podem ter seu assistente de codificação criar e atualizar entradas de conteúdo sem sair de seu editor
- Fluxos de trabalho editorial podem ser automatizados -- um agente de IA que redige conteúdo, verifica contra seu guia de estilo e publica através de seu fluxo de trabalho existente
O protocolo define três primitivas:
- Ferramentas -- Funções que a IA pode chamar (como
create_post,update_entry,search_content) - Recursos -- Dados que a IA pode ler (como seu esquema de conteúdo, posts recentes, biblioteca de mídia)
- Prompts -- Modelos de prompt reutilizáveis para operações comuns
Para integração com CMS, você trabalharará principalmente com Ferramentas e Recursos.
Arquitetura MCP para Integração com CMS
Antes de escrever código, vamos entender a arquitetura. Um servidor MCP fica entre o cliente de IA e seu CMS:
[Cliente de IA] <--Protocolo MCP--> [Servidor MCP] <--REST/GraphQL/SDK--> [Seu CMS]
(Claude) (stdio/SSE) (Node.js) (HTTP/DB) (Payload/WP/Supabase)
O servidor MCP expõe ferramentas e recursos que mapeiam para suas operações de CMS. A camada de transporte pode ser:
- stdio -- Para desenvolvimento local, o cliente de IA executa o servidor MCP como um subprocesso
- SSE (Server-Sent Events) -- Para servidores remotos, usa HTTP com SSE para streaming
- HTTP Streamable -- A opção de transporte mais nova na revisão da especificação 2025
Para servidores de CMS, recomendo começar com stdio para desenvolvimento e passar para SSE ou HTTP Streamable para implantações em produção onde o servidor MCP é executado em sua infraestrutura ao lado do CMS.
O Esqueleto do Servidor Básico
Todo servidor MCP segue o mesmo padrão. Aqui está o esqueleto em TypeScript usando o @modelcontextprotocol/sdk oficial:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({
name: "my-cms-mcp",
version: "1.0.0",
});
// Defina uma ferramenta
server.tool(
"search_content",
"Procure por entradas de conteúdo por consulta",
{
query: z.string().describe("Consulta de pesquisa"),
collection: z.string().describe("Coleção/tipo de post a pesquisar"),
limit: z.number().optional().default(10),
},
async ({ query, collection, limit }) => {
// Sua lógica específica do CMS aqui
const results = await searchCMS(query, collection, limit);
return {
content: [
{
type: "text",
text: JSON.stringify(results, null, 2),
},
],
};
}
);
// Defina um recurso
server.resource(
"schema",
"cms://schema",
async (uri) => ({
contents: [
{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(await getCMSSchema()),
},
],
})
);
const transport = new StdioServerTransport();
await server.connect(transport);
Essa é a fundação. Agora vamos construir implementações reais.
Construindo um Servidor MCP para Payload CMS
Payload é meu CMS favorito para projetos headless (e o usamos muito em nosso trabalho de desenvolvimento de CMS headless). É nativo de TypeScript, possui uma ótima API e sua arquitetura baseada em coleções mapeia lindamente para ferramentas MCP.
Configurando o Servidor Payload MCP
Primeiro, instale as dependências:
mkdir payload-mcp-server && cd payload-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node tsx
Aqui está a implementação completa. A API REST do Payload é limpa o suficiente para que isso seja surpreendentemente direto:
// src/index.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const PAYLOAD_URL = process.env.PAYLOAD_URL || "http://localhost:3000";
const PAYLOAD_API_KEY = process.env.PAYLOAD_API_KEY || "";
const headers = {
"Content-Type": "application/json",
Authorization: `users API-Key ${PAYLOAD_API_KEY}`,
};
async function payloadFetch(path: string, options: RequestInit = {}) {
const res = await fetch(`${PAYLOAD_URL}/api${path}`, {
...options,
headers: { ...headers, ...options.headers },
});
if (!res.ok) {
throw new Error(`Payload API error: ${res.status} ${await res.text()}`);
}
return res.json();
}
const server = new McpServer({
name: "payload-cms-mcp",
version: "1.0.0",
});
// Listar todas as coleções
server.tool(
"list_collections",
"Listar todas as coleções disponíveis do Payload CMS",
{},
async () => {
// Payload não possui um endpoint meta para isso por padrão,
// então acessamos a configuração ou coleções conhecidas de hardcode
const collections = ["posts", "pages", "media", "categories"];
return {
content: [{ type: "text", text: JSON.stringify(collections) }],
};
}
);
// Pesquisar/listar documentos em uma coleção
server.tool(
"find_documents",
"Encontrar documentos em uma coleção Payload com filtragem opcional",
{
collection: z.string().describe("Slug da coleção (ex: 'posts', 'pages')"),
where: z.string().optional().describe("Consulta where do Payload como string JSON"),
limit: z.number().optional().default(10),
page: z.number().optional().default(1),
sort: z.string().optional().describe("Campo a ordenar, prefixe com - para desc"),
},
async ({ collection, where, limit, page, sort }) => {
const params = new URLSearchParams();
params.set("limit", String(limit));
params.set("page", String(page));
if (sort) params.set("sort", sort);
if (where) {
try {
const whereObj = JSON.parse(where);
// Converter para formato de string de consulta do Payload
for (const [key, val] of Object.entries(whereObj)) {
if (typeof val === "object" && val !== null) {
for (const [op, v] of Object.entries(val as Record<string, unknown>)) {
params.set(`where[${key}][${op}]`, String(v));
}
} else {
params.set(`where[${key}][equals]`, String(val));
}
}
} catch {
return { content: [{ type: "text", text: "JSON where inválido" }] };
}
}
const data = await payloadFetch(`/${collection}?${params}`);
return {
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
};
}
);
// Obter um único documento
server.tool(
"get_document",
"Obter um único documento por ID de uma coleção Payload",
{
collection: z.string(),
id: z.string().describe("ID do documento"),
},
async ({ collection, id }) => {
const data = await payloadFetch(`/${collection}/${id}`);
return {
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
};
}
);
// Criar um documento
server.tool(
"create_document",
"Criar um novo documento em uma coleção Payload",
{
collection: z.string(),
data: z.string().describe("Dados do documento como string JSON"),
},
async ({ collection, data }) => {
const result = await payloadFetch(`/${collection}`, {
method: "POST",
body: data,
});
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
);
// Atualizar um documento
server.tool(
"update_document",
"Atualizar um documento existente em uma coleção Payload",
{
collection: z.string(),
id: z.string(),
data: z.string().describe("Dados parciais do documento como string JSON"),
},
async ({ collection, id, data }) => {
const result = await payloadFetch(`/${collection}/${id}`, {
method: "PATCH",
body: data,
});
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
Conectando ao Claude Desktop
Adicione isso à sua configuração do Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json no Mac):
{
"mcpServers": {
"payload-cms": {
"command": "npx",
"args": ["tsx", "/path/to/payload-mcp-server/src/index.ts"],
"env": {
"PAYLOAD_URL": "http://localhost:3000",
"PAYLOAD_API_KEY": "sua-chave-api-aqui"
}
}
}
}
Reinicie o Claude Desktop e você pode agora pedir coisas como "Encontre todos os posts em rascunho em meu CMS" ou "Crie um novo post de blog sobre servidores MCP."
Construindo um Servidor MCP para Supabase
Supabase é interessante porque não é um CMS por padrão -- é um banco de dados Postgres com uma API REST. Mas muitos times a usam como um, especialmente com painéis de administração personalizados construídos em Next.js (algo que fazemos regularmente em nossa prática de desenvolvimento Next.js).
O servidor MCP do Supabase é na verdade o mais poderoso dos três porque você tem acesso direto ao SQL.
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { createClient } from "@supabase/supabase-js";
import { z } from "zod";
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY! // Use service role para lado do servidor
);
const server = new McpServer({
name: "supabase-cms-mcp",
version: "1.0.0",
});
// Listar todas as tabelas (tipos de conteúdo)
server.tool(
"list_tables",
"Listar todas as tabelas no schema público",
{},
async () => {
const { data, error } = await supabase.rpc("get_tables");
// Você precisará de uma função Postgres para isso, ou use:
const { data: tables } = await supabase
.from("information_schema.tables" as any)
.select("table_name")
.eq("table_schema", "public");
return {
content: [{ type: "text", text: JSON.stringify(tables, null, 2) }],
};
}
);
// Consultar conteúdo com filtros
server.tool(
"query_content",
"Consultar linhas de uma tabela com filtros opcionais",
{
table: z.string().describe("Nome da tabela"),
select: z.string().optional().default("*").describe("Colunas a selecionar"),
filters: z.array(z.object({
column: z.string(),
operator: z.enum(["eq", "neq", "gt", "lt", "gte", "lte", "like", "ilike", "is"]),
value: z.string(),
})).optional().describe("Array de condições de filtro"),
limit: z.number().optional().default(20),
orderBy: z.string().optional(),
ascending: z.boolean().optional().default(false),
},
async ({ table, select, filters, limit, orderBy, ascending }) => {
let query = supabase.from(table).select(select).limit(limit);
if (filters) {
for (const f of filters) {
query = query.filter(f.column, f.operator, f.value);
}
}
if (orderBy) {
query = query.order(orderBy, { ascending });
}
const { data, error } = await query;
if (error) {
return { content: [{ type: "text", text: `Erro: ${error.message}` }] };
}
return {
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
};
}
);
// Inserir conteúdo
server.tool(
"insert_row",
"Inserir uma nova linha em uma tabela",
{
table: z.string(),
data: z.string().describe("Dados da linha como string JSON"),
},
async ({ table, data: rowData }) => {
const parsed = JSON.parse(rowData);
const { data, error } = await supabase.from(table).insert(parsed).select();
if (error) {
return { content: [{ type: "text", text: `Erro: ${error.message}` }] };
}
return {
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
};
}
);
// Busca full-text (o recurso matador do Supabase para uso de CMS)
server.tool(
"full_text_search",
"Realizar busca full-text em uma coluna de tabela",
{
table: z.string(),
column: z.string().describe("Coluna com índice tsvector"),
query: z.string().describe("Consulta de pesquisa"),
limit: z.number().optional().default(10),
},
async ({ table, column, query, limit }) => {
const { data, error } = await supabase
.from(table)
.select()
.textSearch(column, query)
.limit(limit);
if (error) {
return { content: [{ type: "text", text: `Erro: ${error.message}` }] };
}
return {
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
};
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
Uma coisa que adoro na abordagem Supabase: como o Supabase expõe seu schema Postgres, você pode criar um recurso que oferece à IA sua definição de schema completa. A IA então sabe exatamente quais campos existem, que tipos eles são e como as tabelas se relacionam. Isso torna suas consultas muito mais precisas.
Construindo um Servidor MCP para WordPress
WordPress. Ainda alimentando 43% da web em 2025. Goste ou não, você vai encontrá-lo.
A API REST do WordPress é na verdade sólida para integração MCP. A parte complicada é autenticação -- WordPress tem como cinco métodos de autenticação diferentes e nenhum deles é ótimo.
Para servidores MCP, recomendo Application Passwords (integrado ao WordPress desde 5.6):
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const WP_URL = process.env.WP_URL!;
const WP_USER = process.env.WP_USER!;
const WP_APP_PASSWORD = process.env.WP_APP_PASSWORD!;
const authHeader = `Basic ${Buffer.from(`${WP_USER}:${WP_APP_PASSWORD}`).toString("base64")}`;
async function wpFetch(endpoint: string, options: RequestInit = {}) {
const res = await fetch(`${WP_URL}/wp-json/wp/v2${endpoint}`, {
...options,
headers: {
"Content-Type": "application/json",
Authorization: authHeader,
...options.headers,
},
});
if (!res.ok) {
throw new Error(`WP API error: ${res.status} ${await res.text()}`);
}
return res.json();
}
const server = new McpServer({
name: "wordpress-mcp",
version: "1.0.0",
});
server.tool(
"search_posts",
"Pesquisar posts do WordPress",
{
search: z.string().optional().describe("Consulta de pesquisa"),
status: z.enum(["publish", "draft", "pending", "private", "any"]).optional().default("any"),
per_page: z.number().optional().default(10),
categories: z.array(z.number()).optional(),
tags: z.array(z.number()).optional(),
after: z.string().optional().describe("Data ISO 8601 -- apenas posts após esta data"),
before: z.string().optional().describe("Data ISO 8601 -- apenas posts antes desta data"),
},
async (params) => {
const searchParams = new URLSearchParams();
if (params.search) searchParams.set("search", params.search);
if (params.status) searchParams.set("status", params.status);
searchParams.set("per_page", String(params.per_page));
if (params.categories) searchParams.set("categories", params.categories.join(","));
if (params.tags) searchParams.set("tags", params.tags.join(","));
if (params.after) searchParams.set("after", params.after);
if (params.before) searchParams.set("before", params.before);
const posts = await wpFetch(`/posts?${searchParams}`);
// Remover HTML do conteúdo para consumo mais limpo pela IA
const cleaned = posts.map((p: any) => ({
id: p.id,
title: p.title.rendered,
slug: p.slug,
status: p.status,
date: p.date,
excerpt: p.excerpt.rendered.replace(/<[^>]*>/g, ""),
content: p.content.rendered.replace(/<[^>]*>/g, ""),
categories: p.categories,
tags: p.tags,
}));
return {
content: [{ type: "text", text: JSON.stringify(cleaned, null, 2) }],
};
}
);
server.tool(
"create_post",
"Criar um novo post do WordPress",
{
title: z.string(),
content: z.string().describe("Conteúdo do post em HTML"),
status: z.enum(["publish", "draft", "pending"]).optional().default("draft"),
categories: z.array(z.number()).optional(),
tags: z.array(z.number()).optional(),
excerpt: z.string().optional(),
},
async (params) => {
const result = await wpFetch("/posts", {
method: "POST",
body: JSON.stringify(params),
});
return {
content: [{
type: "text",
text: `Post criado "${result.title.rendered}" (ID: ${result.id}, Status: ${result.status})`,
}],
};
}
);
server.tool(
"update_post",
"Atualizar um post existente do WordPress",
{
id: z.number().describe("ID do post"),
title: z.string().optional(),
content: z.string().optional(),
status: z.enum(["publish", "draft", "pending", "trash"]).optional(),
excerpt: z.string().optional(),
},
async ({ id, ...updates }) => {
const result = await wpFetch(`/posts/${id}`, {
method: "POST",
body: JSON.stringify(updates),
});
return {
content: [{
type: "text",
text: `Post atualizado "${result.title.rendered}" (ID: ${result.id})`,
}],
};
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
A maior pegadinha com WordPress MCP: o conteúdo volta como HTML renderizado. Você querrá remover tags para a janela de contexto da IA ou você queimará tokens em tags <p>. Aprendi isso do jeito difícil depois de assistir Claude queimar 50k tokens lendo cinco posts do blog.
Comparando as Três Abordagens
| Recurso | Payload CMS | Supabase | WordPress |
|---|---|---|---|
| Complexidade de Autenticação | Simples (chaves de API) | Simples (chave de função de serviço) | Média (Application Passwords) |
| Conhecimento de Schema | Bom (coleções tipadas) | Excelente (schema Postgres completo) | Pobre (sem endpoint de schema) |
| Formato de Conteúdo | JSON limpo | JSON limpo | HTML pesado, precisa sanitizar |
| Suporte de Escrita | Excelente | Excelente | Bom (algumas peculiaridades com blocos) |
| Manipulação de Mídia | Upload via API | Storage API | Endpoint de mídia REST |
| Flexibilidade de Consulta | Bom (consultas Payload) | Excelente (filtragem em nível SQL) | Limitado (parâmetros WP_Query) |
| Opção Auto-hospedada | Sim | Sim (ou cloud) | Sim |
| Tempo de Configuração Típico | 1-2 horas | 1-2 horas | 2-3 horas |
| Melhor Para | Projetos headless modernos | Estruturas de conteúdo personalizadas | Sites existentes de WP |
Considerações de Segurança
Esta é a seção que quase esqueci de escrever, e é argumentavelmente a mais importante.
Quando você dá a um agente de IA acesso de escrita ao seu CMS, você está confiando que ele não vai deletar tudo. Aqui está como não se queimar:
Princípio do Privilégio Mínimo
Crie um usuário/chave de API dedicado para o servidor MCP com permissões mínimas. No Payload, crie uma função que possa acessar apenas coleções específicas. No Supabase, use políticas Row Level Security. No WordPress, crie um usuário com a função Author, não Administrator.
Adicione Confirmação para Operações Destrutivas
Sempre adiciono um parâmetro dryRun para ferramentas de criar/atualizar/deletar. Quando definido como true, a ferramenta retorna o que aconteceria sem fazer de verdade. A IA pode então confirmar com o usuário antes de prosseguir.
server.tool(
"delete_document",
"Deletar um documento (use dryRun primeiro!)",
{
collection: z.string(),
id: z.string(),
dryRun: z.boolean().default(true).describe("Se true, apenas prevê a deleção"),
},
async ({ collection, id, dryRun }) => {
const doc = await payloadFetch(`/${collection}/${id}`);
if (dryRun) {
return {
content: [{
type: "text",
text: `DRY RUN: Deletaria "${doc.title}" (${id}) de ${collection}`,
}],
};
}
await payloadFetch(`/${collection}/${id}`, { method: "DELETE" });
return {
content: [{ type: "text", text: `Deletado "${doc.title}" (${id})` }],
};
}
);
Rate Limiting e Logging de Auditoria
Registre cada invocação de ferramenta. Sério. Você vai querer saber o que seu agente de IA fez às 2 da manhã quando seu editor de conteúdo pergunta por que há 47 novos posts de rascunho sobre pinguins.
Nunca Exponha Credenciais de Banco de Dados Diretamente
O servidor MCP é o limite. A IA nunca vê sua senha de banco de dados ou chave de API -- ela só vê as ferramentas que você expõe.
Casos de Uso do Mundo Real
Aqui estão padrões que vi funcionar bem em produção:
Migração de Conteúdo: Um agente de IA que lê conteúdo de um site WordPress legado, o transforma para corresponder ao seu novo schema Payload e cria entradas no novo CMS. Fizemos isso para um cliente migrando ~3.000 posts. O que teria levado semanas de trabalho manual levou um dia e meio com revisão humana.
Auditoria de SEO: Uma IA que lê todo conteúdo publicado, verifica descrições meta ausentes, conteúdo fraco e links internos quebrados. Escreve suas descobertas de volta como um relatório estruturado no CMS.
Atualizações de Conteúdo em Massa: "Mude todas as referências de 'nosso nome de marca antigo' para 'nosso novo nome de marca' em todas as páginas publicadas." O agente lê, encontra, propõe mudanças e atualiza com aprovação humana.
Gerenciamento de Calendário de Conteúdo: Um agente que conhece seu cronograma de publicação, verifica o que está em rascunho e avisa à equipe sobre próximos prazos -- tudo lendo dados do CMS.
Se você está construindo algo assim e quer ajuda, temos experiência profunda com as três plataformas em nossa prática de CMS headless. Você também pode conferir nossa página de preços para ter uma sensação do que um projeto como este custa.
Perguntas Frequentes
O que é o Model Context Protocol (MCP)? MCP é um protocolo aberto criado pela Anthropic que padroniza como os modelos de IA interagem com fontes de dados e ferramentas externas. Ele usa JSON-RPC 2.0 e define uma arquitetura cliente-servidor onde aplicações de IA (clientes) se conectam a servidores que expõem ferramentas, recursos e prompts. Pense nele como uma porta USB-C para IA -- uma interface padrão que se conecta a muitos sistemas diferentes.
Posso usar MCP com ChatGPT ou apenas Claude? MCP foi criado pela Anthropic, então Claude tem o melhor suporte nativo via Claude Desktop e a API. No entanto, o protocolo é aberto e clientes MCP existem para outras plataformas. OpenAI anunciou suporte a MCP em março de 2025. Cursor, Cline, Windsurf e várias outras ferramentas de desenvolvedor também suportam MCP. Seu servidor funciona com qualquer cliente compatível.
É seguro dar aos agentes de IA acesso de escrita ao meu CMS? Pode ser, com proteções adequadas. Use credenciais de API dedicadas com permissões mínimas, implemente modos dry-run para operações destrutivas, adicione logging de auditoria e considere exigir aprovação humana para ações de publicação. Nunca dê a um servidor MCP acesso em nível de administrador. Trate como você trataria qualquer integração de terceiros -- com limites de confiança apropriados.
Preciso hospedar o servidor MCP separadamente de meu CMS? Para transporte stdio (desenvolvimento local), o servidor MCP é executado como um subprocesso em sua máquina local. Para produção com acesso remoto, você precisará hospedá-lo em algum lugar -- isso pode ser no mesmo servidor do seu CMS, em um container Docker ao seu lado, ou como um serviço separado. A latência entre o servidor MCP e seu CMS importa, então co-localizá-los é ideal.
Como MCP se compara apenas ao uso direto da API REST do CMS com chamada de função? MCP adiciona uma camada de interface padrão. Com chamada de função bruta, você define esquemas personalizados por provedor de IA, trata a autenticação diferentemente para cada um e reconstrói quando os provedores alteram sua API. MCP oferece um servidor que funciona com qualquer cliente compatível. Ele também trata transporte, formatação de erro e descoberta de recursos de uma maneira padronizada. Para uma única integração, a sobrecarga pode não valer a pena. Para qualquer coisa que você reutilizará, absolutamente vale.
Qual CMS funciona melhor com MCP? Supabase oferece a maior flexibilidade porque você tem acesso direto em nível de banco de dados e introspecção completa de schema. Payload CMS é excelente porque sua abordagem first-TypeScript e API REST limpa mapeiam naturalmente para ferramentas MCP. WordPress funciona bem mas requer mais sanitização de conteúdo HTML e tem uma API de consulta menos flexível. A escolha "melhor" depende de sua stack existente.
Posso usar MCP para gerenciar uploads de mídia em meu CMS?
Sim, embora seja um pouco mais envolvido do que conteúdo de texto. Para Payload e WordPress, você pode criar ferramentas que aceitam dados de arquivo codificados em base64 ou URLs e usar as respectivas APIs de upload. Para Supabase, você usaria a Storage API. Recomendo criar ferramentas upload_media e attach_media separadas em vez de tentar lidar com uploads de arquivo dentro de ferramentas de criação de conteúdo.
Quanto custa executar um servidor MCP? O servidor MCP em si é leve -- um simples processo Node.js que usa recursos mínimos. O custo real está nos tokens de IA consumidos quando seu agente lê e escreve conteúdo. Uma operação de conteúdo típica pode usar 2.000-10.000 tokens dependendo de quanto conteúdo está sendo processado. Com os preços atuais de 2025 (Claude Sonnet em $3/$15 por milhão de tokens de entrada/saída), são frações de um centavo por operação. O custo de infraestrutura para hospedar o servidor MCP é negligenciável -- uma pequena VPS ou instância de container é mais que suficiente.