As skills de SEO assentavam em quatro dependências, nenhuma operacional: SEO Tools API (localhost:3000, pasta inexistente em disco), Ahrefs MCP (nunca esteve em config), gsc e lighthouse (em disabledServers). Os seis passos da /seo-audit e o workflow inteiro da /seo-report apontavam para tooling morto — não eram executáveis. - seo-audit v3.0: ordem por custo (grátis → lote pago → unitário pago), gate de mercado (PT = 2620/pt) e gate de maxPages - seo-report v3.0: cobertura de crawl obrigatória no entregável - ferramentas-api.md: inventário das 24 ferramentas OpenSEO + tabela de migração endpoint a endpoint - implementacao-tecnica.md: striking distance, desperdício e filtro de ruído do GSC em código - seo-specialist v3.0: primary_mcps [openseo], bloco de MCPs desligados, FID → INP nos Core Web Vitals maxPages tem default 50: descomplicar.pt auditado com o default deu 25 URLs e 92 issues sem críticos; com maxPages 600 deu 1337 issues e 9 críticos. Documentado como gate — a fase Search Console dimensiona o crawl. Limitações declaradas nas skills: get_audit_issues devolve contagens sem URLs pela bridge MCP (structuredContent.issues não é entregue) e o whoami não expõe saldo de créditos em self-hosted.
5.5 KiB
5.5 KiB
Implementação Técnica — SEO Report (OpenSEO)
Workflow
graph TD
A[Input: URL] --> B[whoami + list_projects]
B --> C{Mercado correcto?}
C -->|Não| C2[Passar locationCode 2620 + languageCode pt]
C -->|Sim| D[GSC: dimensions query]
C2 --> D
D --> E[GSC: dimensions page]
E --> F[Dimensionar crawl:<br/>maxPages = páginas GSC + 25%]
F --> G[run_site_audit]
G --> H[poll get_audit_status]
H -->|running| H
H -->|completed| I[get_audit_issues por severidade]
I --> J[Cruzar: páginas com procura<br/>E com defeito]
J --> K{Fases pagas contratadas?}
K -->|Sim| L[get_keyword_metrics<br/>get_backlinks_overview<br/>get_serp_results]
K -->|Não| M[Saltar fases pagas]
L --> N[Processar]
M --> N
N --> O[Markdown] --> P[Google Doc] --> Q[Partilhar]
O ponto crítico é o nó F. Dimensionar o crawl a partir do GSC é o que distingue um relatório verdadeiro de um relatório limpo e falso.
Sequência de chamadas
// 1. Contexto e mercado
const me = await openseo.whoami();
const projs = await openseo.list_projects();
const projectId = projs[0].id;
const MARKET = { locationCode: 2620, languageCode: 'pt' }; // Portugal
// 2. Search Console — paginar até esgotar (grátis)
async function gscAll(dimensions) {
const rows = [];
let startRow = 0;
while (true) {
const r = await openseo.get_search_console_performance({
projectId, dimensions, dateRange: 'last_3_months',
rowLimit: 1000, startRow
});
const parsed = parseRows(r.text);
rows.push(...parsed);
if (!r.text.split('\n')[0].includes('more available') || !parsed.length) break;
startRow += 1000;
}
return rows;
}
const byQuery = await gscAll(['query']);
const byPage = await gscAll(['page']);
// 3. Dimensionar o crawl a partir da realidade, não de um default
const maxPages = Math.ceil(byPage.length * 1.25);
// 4. Crawl (grátis) — assíncrono, exige poll
const audit = await openseo.run_site_audit({
projectId, url, maxPages, runLighthouse: true
});
let status;
do {
await sleep(30_000);
status = await openseo.get_audit_status({ projectId, auditId: audit.id });
} while (!status.text.includes('completed'));
// 5. Issues por severidade (grátis)
const critical = await openseo.get_audit_issues({ projectId, auditId: audit.id, severity: 'critical' });
const warning = await openseo.get_audit_issues({ projectId, auditId: audit.id, severity: 'warning' });
// 6. Fases pagas — so se contratadas
if (options.paid) {
const kw = await openseo.get_keyword_metrics({
projectId, keywords: strikingDistance(byQuery).map(r => r.key), ...MARKET
});
const bl = await openseo.get_backlinks_overview({ projectId, target: domain });
}
Processamento — as funções que importam
// GSC anonimiza queries: totais por query < totais por página.
// Para números globais usar SEMPRE a vista por página.
const globals = {
clicks: sum(byPage, 'clicks'),
impressions: sum(byPage, 'impressions'),
ctr: sum(byPage, 'clicks') / sum(byPage, 'impressions')
};
// O GSC não filtra por posição — filtrar do lado do cliente.
const strikingDistance = rows => rows
.filter(r => r.position >= 4 && r.position <= 20 && r.impressions >= 25)
.sort((a, b) => b.impressions - a.impressions);
// Páginas com procura real enterradas fora das primeiras 4 páginas.
const waste = rows => rows
.filter(r => r.position > 40 && r.impressions >= 300)
.sort((a, b) => b.impressions - a.impressions);
// O sinal mais accionável: primeira página, CTR nulo.
// Não é ranking — é título e meta description.
const titleProblem = rows => rows
.filter(r => r.position <= 10 && r.impressions >= 200 && r.ctr < 0.005);
// Lixo nas queries do GSC (colagens do Google Ads, operadores site:).
const isNoise = k =>
(k.includes('ativado') && k.includes('correspond')) ||
k.startsWith('-site:') || k.startsWith('=') ||
k.length > 90 || k.includes('0,00');
Entrega
const doc = await googleWorkspace.docs_create_document({
title: `Relatório SEO — ${domain} — ${today}`,
user_google_email: email
});
await googleWorkspace.docs_append_text({ document_id: doc.id, text: markdown });
Notas técnicas
Requisitos
- OpenSEO acessível (
whoamiresponde) - Propriedade ligada ao Search Console
google-workspaceMCP para o documento
Tempos reais (medidos, descomplicar.pt, 07-2026)
| Fase | Duração |
|---|---|
| GSC (2 dimensões, ~1700+474 linhas) | ~10s |
| Crawl 600 páginas + Lighthouse 20 | ~4 min |
get_audit_issues |
instantâneo |
Limitações
- Issues sem URL:
get_audit_issues/get_audit_pagesdevolvem só contagens pela bridge MCP;structuredContent.issuesnão é entregue eget_audit_pagestrunca em ~25 linhas. Usardetails.mcpMeta.urlpara o link da UI. - Sem medidor de créditos:
whoaminão muda depois de chamadas pagas. Custos são estimativas. - Lag do GSC: últimos ~3 dias incompletos; datas em Pacific Time; máx. 16 meses.
Erros comuns
| Erro | Sintoma | Solução |
|---|---|---|
maxPages por omissão (50) |
Poucos issues, relatório "limpo" | Redimensionar pelo GSC, relançar |
| Site fora do GSC | Fase GSC vazia | Gerar sem GSC, aviso destacado |
| Mercado errado | Keywords em inglês/EUA | locationCode: 2620, languageCode: "pt" |
| Cancelar o crawl cedo demais | Sem auditId utilizável |
Fazer poll; 600 páginas ≈ 4 min |
Actualizado: 30-07-2026 | Substitui a implementação baseada em Lighthouse MCP + SEO Tools API + Ahrefs