seo-console-mcp
Un servidor MCP y CLI con licencia MIT que ofrece 43 herramientas para Search Console, App Store, Google Play y WordPress.org. Una fila faltante se reporta como desconocida, nunca como cero.
Documentación
seo-mcp
seo-mcp es un servidor Model Context Protocol por stdio para Google Search Console, PageSpeed Insights y auditorías SEO on-page. Proporciona a los clientes MCP cuarenta y tres herramientas que cubren propiedades verificadas de Search Console y otros lugares donde los productos se descubren: App Store, Google Play, WordPress.org, Google Ads y Core Web Vitals de usuarios reales, mientras mantiene la auditoría HTML, PageSpeed, IndexNow, ideas de palabras clave y las herramientas de WordPress.org utilizables sin credenciales de cuenta de servicio de Google. Cada herramienta también se ejecuta desde la línea de comandos, por lo que un resultado puede escribirse en un archivo en lugar de en el contexto de un modelo, y snapshot registra Search Console, App Store, Google Play y WordPress.org en un momento dado para que una ejecución posterior pueda compararse con él.
https://github.com/user-attachments/assets/66bbd628-d267-421f-9400-633b696bbd53
Requisitos
- Node.js 20.18.1 o superior
gcloudsolo si usas el asistente de configuración
Lo que necesitas depende de las herramientas que uses. El asistente de configuración cubre Search Console y PageSpeed; las herramientas de App Store, Google Play y Chrome UX Report necesitan cada una una credencial que creas tú mismo.
| Herramientas | Necesita | De dónde proviene |
|---|---|---|
seo_audit, audit_site, keyword_ideas (sin siteUrl), wporg_plugin | nada | endpoints públicos |
pagespeed | SEO_MCP_PAGESPEED_KEY opcional | asistente de configuración --pagespeed-key, o una clave API de Google Cloud |
crux_field_data, crux_history | SEO_MCP_CRUX_KEY (o la clave de PageSpeed si puede llamar a la API de CrUX) | clave API de Google Cloud |
indexnow_submit | SEO_MCP_INDEXNOW_KEY | cualquier clave que alojes en /<key>.txt |
Herramientas de Search Console, propiedades snapshot | clave de cuenta de servicio | asistente de configuración, luego agrega la cuenta a la propiedad |
snapshot, list_snapshots, compare_snapshots | SEO_MCP_SNAPSHOT_DIR opcional | donde viven los archivos de instantáneas, por defecto en ~/.config/seo-mcp/snapshots |
verify | CLOUDFLARE_API_TOKEN | Cloudflare, Zone.DNS:Edit |
app_store_listing, app_store_discovery, app_store_reviews | SEO_MCP_ASC_KEY_PATH, SEO_MCP_ASC_KEY_ID, SEO_MCP_ASC_ISSUER_ID | clave de equipo de App Store Connect, cualquier rol que pueda leer la app |
app_store_sales | lo anterior más SEO_MCP_ASC_VENDOR_NUMBER | clave de equipo creada con Admin, Finance o Sales and Reports |
play_store_stats | SEO_MCP_PLAY_BUCKET, SEO_MCP_PLAY_CREDENTIALS | cuenta de servicio con acceso de lectura al bucket de informes |
play_vitals | SEO_MCP_PLAY_CREDENTIALS | cuenta de servicio invitada en Play Console con acceso a calidad de la app |
las herramientas ads_ | GOOGLE_ADS_DEVELOPER_TOKEN, GOOGLE_ADS_CLIENT_ID, GOOGLE_ADS_CLIENT_SECRET, GOOGLE_ADS_REFRESH_TOKEN, GOOGLE_ADS_CUSTOMER_ID | un token de desarrollador de Google Ads y un cliente OAuth con token de actualización |
Instalación y compilación
npm install
npm run build
Ejecuta el servidor local con:
node /absolute/path/to/seo-mcp/dist/index.js
El paquete se publica en npm como seo-console-mcp; instala un comando llamado seo-mcp. Un cliente MCP puede iniciarlo mediante:
npx -y seo-console-mcp
El servidor en ejecución usa stdout exclusivamente para el protocolo de cable MCP. Los diagnósticos se escriben en stderr.
Asistente de configuración
Desde un checkout local:
npm run setup
O con npx:
npx -y seo-console-mcp setup
Para una elección de proyecto desatendida o una ubicación de clave personalizada:
seo-mcp setup --project my-seo-project --key /absolute/path/seo-mcp.key.json
El asistente también ofrece una clave API opcional de PageSpeed Insights para una cuota más alta. Es opcional: usa --pagespeed-key para crearla sin aviso, o --no-pagespeed-key para omitir el aviso explícitamente. Las ejecuciones no interactivas la omiten a menos que se proporcione --pagespeed-key.
El asistente es seguro de volver a ejecutar. Este:
- Verifica
gcloud. Si está ausente, imprime instrucciones manuales y sale correctamente sin cambiar nada. - Usa la cuenta autenticada activa o ejecuta
gcloud auth login. - Usa el proyecto actual, un
--projectproporcionado, o pide un ID de proyecto. Crea el proyecto si no existe y lo selecciona. - Habilita
searchconsole.googleapis.com,pagespeedonline.googleapis.comysiteverification.googleapis.com. - Reutiliza o crea la cuenta de servicio
seo-mcp. - Reutiliza una clave existente o crea
seo-mcp.key.json. - Opcionalmente crea una clave API a nivel de proyecto restringida a PageSpeed Insights.
- Imprime el paso requerido de permiso de Search Console y las configuraciones de cliente listas para copiar.
El asistente nunca imprime el contenido de la clave de la cuenta de servicio. Cuando se solicita la creación de la clave de PageSpeed y tiene éxito, imprime esa clave una vez en la configuración final del cliente. El nombre de archivo *.key.json generado es ignorado por Git.
Otorgar acceso a Search Console a la cuenta de servicio
La API de Search Console no tiene un endpoint para agregar un usuario a una propiedad, por lo que la cuenta de servicio debe convertirse en propietaria verificada del dominio en sí. Hay dos formas de hacerlo.
Automatizado (DNS de Cloudflare)
Si el DNS del dominio está en Cloudflare, verify hace todo: pide a Google un token de verificación, escribe el registro TXT a través de la API de Cloudflare, espera la verificación y registra la propiedad.
export CLOUDFLARE_API_TOKEN=... # a token scoped to Zone.DNS:Edit for the zone
seo-mcp verify getpsst.app another-domain.com
El token también se puede pasar con --cf-token, y la ruta de la clave con --credentials (de lo contrario, se usa GOOGLE_APPLICATION_CREDENTIALS / SEO_MCP_CREDENTIALS). El comando es idempotente: el registro TXT se deja en su lugar (Google lo vuelve a verificar), por lo que volver a ejecutar un dominio es seguro. Deja el registro en el DNS o se pierde la propiedad.
verify lee el token de CLOUDFLARE_API_TOKEN o CF_API_TOKEN (o --cf-token) y nunca lo almacena ni lo registra, por lo que cualquier almacén de secretos que pueda exportar una variable de entorno funciona. El token necesita Zone -> DNS -> Edit y Zone -> Zone -> Read (la plantilla "Edit zone DNS"), con ámbito en las zonas que verificas. Para mantenerlo fuera del historial del shell:
macOS (Keychain):
security add-generic-password -a "$USER" -s cloudflare-dns-edit -l "Cloudflare DNS Edit" -U -w # store once, hidden prompt
CLOUDFLARE_API_TOKEN=$(security find-generic-password -s cloudflare-dns-edit -w) seo-mcp verify example.com
Linux (libsecret, o pass):
secret-tool store --label="Cloudflare DNS Edit" service cloudflare-dns-edit # store once, hidden prompt
CLOUDFLARE_API_TOKEN=$(secret-tool lookup service cloudflare-dns-edit) seo-mcp verify example.com
Windows (PowerShell SecretManagement):
Set-Secret -Name cloudflare-dns-edit -Secret (Read-Host -AsSecureString) # store once, hidden prompt
$env:CLOUDFLARE_API_TOKEN = Get-Secret -Name cloudflare-dns-edit -AsPlainText; seo-mcp verify example.com
Manual
Agrega la cuenta de servicio como propietaria en la interfaz de Search Console:
Search Console -> your property -> Settings -> Users and permissions -> Add user
seo-mcp@PROJECT_ID.iam.gserviceaccount.com -> Owner
Usa el correo electrónico exacto de la cuenta de servicio impreso por el asistente. Se necesita acceso de propietario porque submit_sitemap es una operación de escritura.
Respaldo manual de Google Cloud
Si gcloud no está disponible, crea las credenciales manualmente o ejecuta estos comandos después de instalarlo:
gcloud auth login
gcloud projects create YOUR_PROJECT_ID
gcloud config set project YOUR_PROJECT_ID
gcloud services enable searchconsole.googleapis.com pagespeedonline.googleapis.com siteverification.googleapis.com
gcloud iam service-accounts create seo-mcp --display-name="SEO MCP"
gcloud iam service-accounts keys create ./seo-mcp.key.json \
--iam-account=seo-mcp@YOUR_PROJECT_ID.iam.gserviceaccount.com
Si el proyecto ya existe, omite gcloud projects create. Luego otorga acceso a Search Console a la cuenta de servicio (ver arriba) y configura la ruta absoluta de la clave en el cliente MCP.
Autenticación
Las herramientas de Search Console usan google.auth.GoogleAuth con ambos ámbitos:
https://www.googleapis.com/auth/webmastershttps://www.googleapis.com/auth/webmasters.readonly
El orden de búsqueda de credenciales es:
--credentials /absolute/path/key.jsonSEO_MCP_CREDENTIALSGOOGLE_APPLICATION_CREDENTIALS~/.config/seo-mcp/seo-mcp.key.json(o$XDG_CONFIG_HOME/seo-mcp/...) si existe. Esta es la ubicación predeterminada donde escribe el asistente de configuración, por lo que una instalación estándar no necesita configuración.
Por ejemplo:
node dist/index.js --credentials /absolute/path/seo-mcp.key.json
pagespeed es público y no usa la cuenta de servicio. Establece SEO_MCP_PAGESPEED_KEY o pasa apiKey a esa herramienta para una cuota más alta de PageSpeed Insights. seo_audit, audit_site y indexnow_submit tampoco necesitan credenciales de Google; indexnow_submit en su lugar toma una clave de IndexNow mediante key o SEO_MCP_INDEXNOW_KEY. keyword_ideas solo las necesita cuando se pasa siteUrl para la referencia cruzada de Search Console. App Store Sales and Trends lee SEO_MCP_ASC_VENDOR_NUMBER. Las herramientas de Chrome UX Report leen SEO_MCP_CRUX_KEY, con respaldo a SEO_MCP_PAGESPEED_KEY cuando a la misma clave se le permite llamar a chromeuxreport.googleapis.com. snapshot, list_snapshots y compare_snapshots mantienen sus documentos en SEO_MCP_SNAPSHOT_DIR, por defecto en ~/.config/seo-mcp/snapshots, y no pueden leer ni escribir fuera de él. La tabla bajo Requisitos asigna cada herramienta a lo que necesita.
Modelo de seguridad
- Verificar un dominio convierte a la cuenta de servicio en Propietaria verificada. Los propietarios pueden cambiar la configuración de Search Console y enviar solicitudes de eliminación (deindexación), por lo que trata la clave como una credencial sensible aunque la mayoría de las herramientas aquí solo leen.
- Mantén la clave local. Vive en la ruta
GOOGLE_APPLICATION_CREDENTIALS(se recomiendachmod 600). Nunca la incluyas en un paquete publicado, una imagen de contenedor o un almacén de secretos de CI. Si se filtra, cualquiera que la tenga tiene control de propietario sobre cada propiedad verificada. - Deja el registro TXT
google-site-verificationen el DNS. Google lo vuelve a verificar; eliminarlo revoca la propiedad. - Ningún secreto se registra. El asistente y
verifyimprimen solo rutas de credenciales, nunca contenidos de claves o tokens. - Revocar es fácil. Renuncia a la propiedad desde la interfaz de Search Console (o
siteVerification.webResource.delete), y rota la clave congcloud iam service-accounts keys delete. seo_auditsolo obtiene hosts públicos. La URL objetivo y cada salto de redirección se resuelve y se rechaza si aterriza en una dirección de bucle local, privada, de enlace local u otra no pública, por lo que un modelo no puede ser dirigido a obtener servicios internos o metadatos de la nube. La dirección se valida nuevamente en el momento de la conexión (el socket se fija a la dirección validada), por lo que un host de rebinding de DNS no puede presentar una dirección pública en la validación y una privada en la conexión. EstableceSEO_MCP_ALLOW_PRIVATE_HOSTS=1para auditar hosts internos o de staging en los que confíes. Esto no sustituye el aislamiento a nivel de red; ejecuta el servidor detrás de controles de salida si auditas URLs no confiables en un host con servicios internos alcanzables.
Plugin de Claude Code
Este repositorio también es un plugin de Claude Code que agrupa el servidor MCP y agrega tres comandos de barra sobre él. Desde Claude Code:
/plugin marketplace add ibrahimhajjaj/seo-console-mcp
/plugin install seo-console@verdelic
Registra el servidor MCP (mediante npx -y seo-console-mcp) y agrega:
/seo-console:triage <siteUrl>: triaje completo de propiedad con un plan de acción priorizado/seo-console:content <siteUrl>: contenido para crear o mejorar, respaldado por datos de Search Console/seo-console:launch <siteUrl>: verificación de preparación SEO previa al lanzamiento / lanzamiento
El servidor encuentra tu clave de cuenta de servicio automáticamente en la ubicación predeterminada (~/.config/seo-mcp/seo-mcp.key.json, donde el asistente de configuración la escribe), por lo que no se necesita configuración para una instalación estándar. Para una clave en otro lugar, establece GOOGLE_APPLICATION_CREDENTIALS (y SEO_MCP_PAGESPEED_KEY para una cuota más alta de PageSpeed) en el entorno donde se ejecuta Claude Code. Las herramientas seo_audit y pagespeed funcionan sin credenciales en absoluto.
Para probarlo desde un checkout local sin un marketplace: claude --plugin-dir ..
Claude Code (solo servidor MCP)
Registra la compilación local para el usuario actual:
claude mcp add --scope user seo-mcp --env GOOGLE_APPLICATION_CREDENTIALS=/abs/path/seo-mcp.key.json -- node /abs/path/seo-mcp/dist/index.js
El separador -- es obligatorio. Separa las opciones de Claude Code del comando del servidor MCP.
O con npx (sin compilación local):
claude mcp add --scope user seo-mcp --env GOOGLE_APPLICATION_CREDENTIALS=/abs/path/seo-mcp.key.json -- npx -y seo-console-mcp
El ámbito de usuario hace que el servidor esté disponible en todos tus proyectos. Usa --scope project cuando el registro deba compartirse a través del .mcp.json del proyecto actual en su lugar.
.mcp.json del proyecto:
{
"mcpServers": {
"seo-mcp": {
"type": "stdio",
"command": "node",
"args": ["/abs/path/seo-mcp/dist/index.js"],
"env": {
"GOOGLE_APPLICATION_CREDENTIALS": "/abs/path/seo-mcp.key.json"
}
}
}
}
Claude Desktop
Agrega la misma entrada de servidor bajo mcpServers en el archivo de configuración de Claude Desktop, luego reinicia Claude Desktop:
{
"mcpServers": {
"seo-mcp": {
"command": "node",
"args": ["/abs/path/seo-mcp/dist/index.js"],
"env": {
"GOOGLE_APPLICATION_CREDENTIALS": "/abs/path/seo-mcp.key.json"
}
}
}
}
Para ejecutar sin una compilación local, usa "command": "npx" y "args": ["-y", "seo-console-mcp"].
Recursos
seo://properties devuelve las propiedades de Google Search Console disponibles para la cuenta de servicio como JSON. Llama a Search Console en cada lectura, por lo que el resultado siempre está actualizado.
Prompts
Los clientes MCP muestran estos prompts como puntos de partida que un usuario puede elegir para flujos de trabajo SEO comunes:
seo_triageconfirma una propiedad, analiza el rendimiento reciente y las oportunidades, audita el sitio y produce un plan de acción de impacto versus esfuerzo.content_opportunitiesagrupa recomendaciones respaldadas por evidencia en contenido para crear y contenido existente para mejorar.launch_seo_checkproduce una lista de verificación de aprobación/rechazo para la preparación técnica y de indexación antes del lanzamiento.
Herramientas
Cada herramienta valida su entrada con Zod. Los fallos de herramientas devuelven un resultado de error MCP en lugar de terminar el servidor. El estado, mensaje y razón de la API de Google se incluyen cuando están disponibles. Un 403 de Search Console también explica cómo otorgar acceso a la propiedad a la cuenta de servicio.
list_properties
Lista cada propiedad de Google Search Console a la que la cuenta de servicio puede acceder, devolviendo el siteUrl y permissionLevel exactos de cada propiedad. No recibe ninguna entrada. Se requieren credenciales de cuenta de servicio, a diferencia de pagespeed, seo_audit, audit_site y indexnow_submit.
Esta herramienta no acepta parámetros.
search_analytics
Consulta searchanalytics.query y devuelve una tabla clasificada compacta junto con filas estructuradas.
{
"siteUrl": "sc-domain:example.com",
"startDate": "2026-06-01",
"endDate": "2026-06-28",
"dimensions": ["query", "page"],
"rowLimit": 100,
"maxTableRows": 25,
"dimensionFilterGroups": [
{
"groupType": "and",
"filters": [
{ "dimension": "query", "operator": "contains", "expression": "seo" }
]
}
],
"type": "web"
}
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|---|
siteUrl | string | sí | Propiedad de Search Console, como https://example.com/ o sc-domain:example.com | |
startDate | string | no | Fecha de inicio en formato YYYY-MM-DD; el valor predeterminado es hace 28 días | |
endDate | string | no | Fecha de fin en formato YYYY-MM-DD; el valor predeterminado es hoy | |
dimensions | lista de uno de query, page, country, device, date, searchAppearance | no | ["query"] | Dimensiones utilizadas para agrupar resultados |
rowLimit | number | no | 25 | Número máximo de filas a devolver |
startRow | number | no | 0 | Fila de inicio basada en cero, para paginar un resultado grande |
maxTableRows | number | no | 25 | Límite de filas mostradas en la tabla de texto; las filas estructuradas siempre están completas. 0 = solo resumen. |
dimensionFilterGroups | lista JSON | no | Filtros de dimensión de Search Console | |
type | uno de web, image, video, news, discover, googleNews | no | Tipo de resultado. discover es el feed de Discover y googleNews es la aplicación Google News y news.google.com, no la pestaña Noticias en Búsqueda. Ambos admiten menos dimensiones que web: ninguno informa una dimensión de consulta | |
dataState | uno de full, all | no | full = datos finalizados (predeterminado, retraso de ~2-3 días); all = incluir datos parciales recientes | |
aggregationType | uno de auto, byProperty, byPage | no | Cómo agrega Search Console las filas |
maxTableRows limita solo la tabla de texto; las filas estructuradas permanecen completas, por lo que 0 devuelve los totales sin tabla en lugar de un resultado vacío. discover y googleNews admiten menos dimensiones que web: ninguno informa una dimensión de query.
keyword_ideas
Expande una semilla a través del Autocompletado de Google y devuelve ideas de palabras clave normalizadas y deduplicadas agrupadas por familia de descubrimiento. Utiliza el endpoint público de autocompletado, no necesita una clave API adicional y funciona sin credenciales de Google a menos que se proporcione siteUrl. Con una propiedad de Search Console, etiqueta las ideas que ya están posicionadas con su posición promedio, clics e impresiones durante la ventana de retroceso seleccionada.
{
"seed": "technical seo",
"siteUrl": "sc-domain:example.com",
"language": "en",
"country": "us",
"expansions": ["alphabet", "questions", "prepositions", "comparisons"],
"days": 90,
"limit": 100
}
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|---|
seed | string | sí | Palabra clave semilla a expandir | |
siteUrl | string | no | Propiedad opcional de Search Console utilizada para identificar consultas ya posicionadas | |
language | string | no | "en" | Idioma de la interfaz de autocompletado pasado como hl |
country | string | no | País de autocompletado pasado como gl | |
expansions | lista de uno de alphabet, questions, prepositions, comparisons | no | ["alphabet","questions","prepositions","comparisons"] | Familias de expansión de sugerencias a ejecutar además de la semilla simple |
days | number | no | 90 | Ventana de retroceso de Search Console en días |
limit | number | no | 100 | Número máximo de ideas de palabras clave a devolver |
Las cuatro familias de expansión se ejecutan de forma predeterminada. days tiene un valor predeterminado de 90 y un máximo de 480; limit tiene un valor predeterminado de 100 y un máximo de 500. Los fallos individuales de autocompletado se cuentan sin descartar las sugerencias exitosas.
search_opportunities
Encuentra consultas de alta impresión a una distancia notable de posiciones más fuertes. Agrupa por consulta y página, se predetermina a las posiciones 5 a 20 y devuelve oportunidades clasificadas por posición ponderada por impresiones.
{
"siteUrl": "sc-domain:example.com",
"startDate": "2026-06-01",
"endDate": "2026-06-28",
"minPosition": 5,
"maxPosition": 20,
"minImpressions": 100,
"limit": 25
}
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|---|
siteUrl | string | sí | Propiedad de Search Console a analizar | |
startDate | string | no | Fecha de inicio en formato YYYY-MM-DD; el valor predeterminado es la ventana más reciente de 28 días | |
endDate | string | no | Fecha de fin en formato YYYY-MM-DD; el valor predeterminado es hoy | |
minPosition | number | no | Posición promedio más baja a incluir; el valor predeterminado es 5 | |
maxPosition | number | no | Posición promedio más alta a incluir; el valor predeterminado es 20 | |
minImpressions | number | no | Impresiones mínimas requeridas; el valor predeterminado es 10 | |
limit | number | no | Número máximo de oportunidades a devolver; el valor predeterminado es 50 |
compare_search_periods
Compara una ventana seleccionada con la ventana inmediatamente anterior de igual duración. Devuelve los mayores ganadores y perdedores de clics agrupados por consulta o página.
{
"siteUrl": "sc-domain:example.com",
"startDate": "2026-06-01",
"endDate": "2026-06-28",
"by": "query",
"limit": 25
}
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|---|
siteUrl | string | sí | Propiedad de Search Console a analizar | |
startDate | string | no | Fecha de inicio en formato YYYY-MM-DD; el valor predeterminado es la ventana más reciente de 28 días | |
endDate | string | no | Fecha de fin en formato YYYY-MM-DD; el valor predeterminado es hoy | |
by | uno de query, page | no | "query" | Dimensión utilizada para comparar el rendimiento |
limit | number | no | Número máximo de ganadores y perdedores a devolver; el valor predeterminado es 50 de cada uno |
ctr_gaps
Encuentra consultas o páginas de alta impresión cuyo CTR está por debajo del promedio de filas en la misma posición redondeada. La estimación de clics perdidos ayuda a priorizar las reescrituras de títulos y descripciones.
{
"siteUrl": "sc-domain:example.com",
"by": "page",
"minImpressions": 250,
"limit": 25
}
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|---|
siteUrl | string | sí | Propiedad de Search Console a analizar | |
startDate | string | no | Fecha de inicio en formato YYYY-MM-DD; el valor predeterminado es la ventana más reciente de 28 días | |
endDate | string | no | Fecha de fin en formato YYYY-MM-DD; el valor predeterminado es hoy | |
by | uno de query, page | no | "query" | Dimensión utilizada para identificar brechas de CTR |
minImpressions | number | no | Impresiones mínimas requeridas; el valor predeterminado es 100 | |
limit | number | no | Número máximo de brechas a devolver; el valor predeterminado es 50 |
query_cannibalization
Encuentra consultas para las cuales varias páginas reciben impresiones de Search Console. Los resultados agrupan las páginas en competencia y clasifican los grupos por impresiones totales.
{
"siteUrl": "sc-domain:example.com",
"startDate": "2026-06-01",
"endDate": "2026-06-28",
"minImpressions": 25
}
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|---|
siteUrl | string | sí | Propiedad de Search Console a analizar | |
startDate | string | no | Fecha de inicio en formato YYYY-MM-DD; el valor predeterminado es la ventana más reciente de 28 días | |
endDate | string | no | Fecha de fin en formato YYYY-MM-DD; el valor predeterminado es hoy | |
minImpressions | number | no | Impresiones mínimas por fila de consulta-página; el valor predeterminado es 10 |
list_sitemaps
Lista la ruta del sitemap, los tiempos de envío/descarga, los indicadores de pendiente/índice, los recuentos de advertencias/errores y los recuentos de contenido.
{
"siteUrl": "https://www.example.com/"
}
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|---|
siteUrl | string | sí | Propiedad de Search Console |
submit_sitemap
Envía un sitemap y actualiza su estado actual. Esta es una operación de escritura. Si el envío tiene éxito pero la actualización de estado falla, el resultado aún confirma que Google aceptó la escritura e informa la advertencia de actualización.
{
"siteUrl": "sc-domain:example.com",
"feedpath": "https://www.example.com/sitemap.xml"
}
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|---|
siteUrl | string | sí | Propiedad de Search Console | |
feedpath | string | sí | URL absoluta del sitemap a enviar | |
dryRun | boolean | no | false | Si es true, informa lo que se enviaría sin escribir en Search Console |
delete_sitemap
Elimina un sitemap enviado de una propiedad de Search Console. Esta es una operación de escritura. Establece dryRun en true para previsualizar la eliminación sin cambiar Search Console.
{
"siteUrl": "sc-domain:example.com",
"feedpath": "https://www.example.com/sitemap.xml",
"dryRun": true
}
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|---|
siteUrl | string | sí | Propiedad de Search Console | |
feedpath | string | sí | URL absoluta del sitemap a eliminar | |
dryRun | boolean | no | false | Si es true, informa lo que se eliminaría sin escribir en Search Console |
inspect_url
Devuelve la cobertura del índice, el veredicto, el estado de robots, el estado de indexación, el tiempo de rastreo, el estado de obtención, los canónicos de Google y del usuario, la usabilidad móvil y el estado de resultados enriquecidos.
{
"siteUrl": "sc-domain:example.com",
"inspectionUrl": "https://www.example.com/products/widget"
}
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|---|
siteUrl | string | sí | Propiedad de Search Console que contiene la URL inspeccionada | |
inspectionUrl | string | sí | URL completamente calificada a inspeccionar |
index_coverage
Obtiene un sitemap y verifica un conjunto limitado de sus URL de página directas con la API de inspección de URL de Google. Devuelve recuentos de indexadas, no indexadas y fallidas, las URL no indexadas y sus estados de cobertura, resultados completos por URL y si el resultado fue truncado. Los índices de sitemap no se siguen hacia sitemaps secundarios.
{
"siteUrl": "sc-domain:example.com",
"sitemapUrl": "https://www.example.com/sitemap.xml",
"maxUrls": 20,
"concurrency": 3
}
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|---|
siteUrl | string | sí | Propiedad de Search Console que contiene las URL del sitemap | |
sitemapUrl | string | sí | URL del sitemap completamente calificada a inspeccionar | |
maxUrls | number | no | 20 | Número máximo de URL a inspeccionar |
concurrency | number | no | 3 | Solicitudes de inspección de URL concurrentes |
maxUrls tiene un valor predeterminado de 20 y un máximo absoluto de 50. concurrency tiene un valor predeterminado de 3 y un máximo absoluto de 5. Estos límites protegen la cuota de la API de inspección de URL, que es de aproximadamente 2,000 consultas por día y 600 por minuto para cada propiedad.
request_recrawl
Verifica URL con la API de inspección de URL y, cuando algunas no están indexadas, vuelve a enviar el sitemap que las cubre. Ese reenvío es la única señal de recrawleo masivo compatible con Google: no existe una API de solicitud de indexación, y el botón Solicitar indexación de la interfaz de Search Console no tiene equivalente programático. Las URL provienen de urls o se leen de sitemapUrl; el sitemap a reenviar es feedpath, con valor predeterminado sitemapUrl. Esta es una operación de escritura. Establece dryRun en true para inspeccionar e informar sin reenviar.
{
"siteUrl": "sc-domain:example.com",
"sitemapUrl": "https://www.example.com/sitemap.xml",
"maxUrls": 20,
"dryRun": true
}
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|---|
siteUrl | string | sí | Propiedad de Search Console que contiene las URL | |
urls | lista de string | no | URL explícitas a verificar; omítelas para leerlas de sitemapUrl | |
sitemapUrl | string | no | Sitemap del cual leer las URL; también es el sitemap predeterminado a reenviar | |
feedpath | string | no | Sitemap a reenviar cuando se encuentren URL no indexadas; el valor predeterminado es sitemapUrl | |
maxUrls | number | no | 20 | Número máximo de URL del sitemap a inspeccionar |
concurrency | number | no | 3 | Solicitudes de inspección de URL concurrentes |
dryRun | boolean | no | false | Si es true, inspecciona e informa sin reenviar el sitemap |
Comparte los límites de index_coverage (maxUrls hasta 50, concurrency hasta 5) porque ambos usan la misma cuota de Inspección de URL. El reenvío solo provoca un rastreo nuevo de páginas cuyo lastmod del sitemap esté fresco, así que mantén lastmod preciso para las URL cambiadas.
indexnow_submit
Envía hasta 10,000 URL cambiadas en una sola llamada a un endpoint de IndexNow. Los motores participantes (Bing, Yandex, Naver, Seznam, Yep) comparten los envíos entre sí. Google no usa IndexNow; usa request_recrawl para Google. Esta es una operación de escritura y admite dryRun. No necesita credenciales de Google.
{
"urls": ["https://www.example.com/new-page", "https://www.example.com/updated-page"],
"key": "your-indexnow-key"
}
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|---|
urls | lista de cadenas | sí | URL de páginas cambiadas; un envío cubre un solo host | |
key | cadena | no | Clave de IndexNow; por defecto SEO_MCP_INDEXNOW_KEY. La misma clave debe estar alojada en el sitio como archivo de texto en https:///.txt (o en keyLocation) que contenga solo la clave | |
keyLocation | cadena | no | URL del archivo de clave alojado cuando no está en https:///.txt | |
endpoint | uno de api.indexnow.org, www.bing.com, yandex.com, searchadvisor.naver.com, search.seznam.cz, indexnow.yep.com | no | "api.indexnow.org" | Endpoint de IndexNow a notificar; los motores participantes comparten envíos |
dryRun | booleano | no | false | Si es verdadero, informa lo que se enviaría sin notificar al endpoint |
Todas las URL en un envío deben compartir un solo host. La clave es cualquier valor de 8 a 128 caracteres de letras, dígitos o guiones, pasada como key o SEO_MCP_INDEXNOW_KEY, y debe estar alojada como archivo de texto que contenga exactamente la clave en https://<host>/<key>.txt (o en keyLocation en el mismo host). Como las URL de archivos de clave convencionalmente contienen la clave, ni la clave ni keyLocation se muestran nunca en la salida de la herramienta. endpoint por defecto es api.indexnow.org; un envío a cualquier endpoint participante llega a todos ellos.
pagespeed
Devuelve datos de campo de CrUX cuando están disponibles, incluidos LCP, CLS, INP o FID, FCP y TTFB. También devuelve puntuaciones de categorías de Lighthouse y hasta diez oportunidades de mayor ahorro.
{
"url": "https://www.example.com/",
"strategy": "mobile",
"category": ["performance", "seo", "accessibility", "best-practices"]
}
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|---|
url | cadena | sí | URL de página pública a analizar | |
strategy | uno de mobile, desktop | no | "mobile" | Estrategia de dispositivo de Lighthouse |
category | lista de uno de performance, seo, accessibility, best-practices | no | ["performance","seo","accessibility","best-practices"] | Categorías de Lighthouse a ejecutar |
apiKey | cadena | no | Clave opcional de API de PageSpeed Insights; por defecto SEO_MCP_PAGESPEED_KEY |
strategy por defecto es mobile. Las cuatro categorías se solicitan por defecto. apiKey es opcional y anula SEO_MCP_PAGESPEED_KEY para esa llamada.
seo_audit
Obtiene hasta 10 MB de HTML con redirecciones habilitadas, un tiempo de espera de 15 segundos y un agente de usuario identificativo. Extrae longitudes de título y descripción, canonical, robots, H1 y esquema de encabezados, etiquetas de Open Graph y Twitter, tipos de JSON-LD, cobertura de texto alternativo de imágenes, enlaces internos/externos, recuento de palabras, idioma y viewport. Señala títulos faltantes o duplicados, descripción faltante, H1 faltantes o múltiples, canonical faltante y JSON-LD faltante o inválido.
{
"url": "https://www.example.com/landing-page"
}
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|---|
url | cadena | sí | URL de página pública a auditar |
audit_site
Obtiene un sitemap y audita hasta 50 de sus URL de página con concurrencia limitada. Los índices de sitemap se admiten con un límite máximo de cinco obtenciones de sitemaps hijos. El resultado incluye hallazgos compactos por página, errores aislados de obtención de páginas, un recuento de cada problema compartido y recuentos explícitos de truncamiento y omisión. No requiere credenciales de Google.
{
"sitemapUrl": "https://www.example.com/sitemap.xml",
"maxPages": 20,
"concurrency": 5
}
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|---|
sitemapUrl | cadena | sí | URL de sitemap público a auditar | |
maxPages | número | no | 20 | Máximo de páginas a auditar |
concurrency | número | no | 5 | Máximo de obtenciones de páginas en vuelo |
maxPages por defecto es 20 y concurrency por defecto es 5. Sus valores máximos son 50 y 10, respectivamente.
server_version
Qué compilación del servidor está respondiendo, desde dónde se ejecuta y si salió de una caché de npx. Sin credenciales.
Esta herramienta no toma parámetros.
Cuatro valores parecen este y no lo son: lo que npm llama latest, a lo que resuelve el rango de versiones, lo que declara el manifiesto del plugin y lo que realmente se ejecuta. Los primeros tres son todos legibles y ninguno responde la pregunta. Verificar la herramienta de línea de comandos tampoco es un sustituto, ya que es un proceso separado resuelto por separado y puede ser una compilación diferente en la misma máquina.
La ruta de instalación es la pista. npx reutiliza una compilación en caché sin volver a resolver el rango y sin errores, por lo que un servidor puede ir por detrás de la versión publicada mientras que cada otra señal lee actual; una ruta bajo _npx es lo que lo muestra.
Llámalo después de actualizar, antes de informar cualquier cosa. npm view <pkg> version lee una caché de registro local y puede devolver la versión anterior durante minutos después de una publicación exitosa, mientras que dist-tags y la matriz de versiones ya llevan la nueva. Dos sesiones aquí concluyeron independientemente que una publicación había fallado cuando no lo había hecho, en versiones separadas. Una lectura de registro no puede distinguir una publicación lenta de una fallida; preguntar al proceso en ejecución qué es puede hacerlo.
wporg_plugin
Busca un plugin de WordPress.org por slug y devuelve instalaciones activas, descargas, calificaciones, hilos de soporte y fechas de versión. Usa la API pública de wp.org y no necesita credenciales ni clave de API. Un plugin publicado en los últimos días se informa con possiblyLagging: true cuando un campo parece vacío, porque la API de wp.org subinforma plugins recientes; el campo puede estar ya visible en la página.
{ "slug": "akismet" }
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|---|
slug | cadena | sí | Slug de plugin de WordPress.org, p. ej. akismet | |
downloadDays | número | no | 30 | Días de historial de descargas diarias a obtener; 0 lo omite |
includeVersionDistribution | booleano | no | true | También obtener la cuota de instalaciones activas en cada versión del plugin |
play_store_stats
Lee los informes masivos de Google Play para una aplicación y devuelve Instalaciones de Dispositivos Activos, además de visitantes de la ficha de la tienda y adquisiciones agrupadas por fuente de tráfico y término de búsqueda. hasPlaySearchRows indica explícitamente si aparece algún tráfico de búsqueda de Play, ya que su ausencia es un hallazgo más que un error. Los informes tienen un retraso de días, por lo que lastDatePresent es la última fecha realmente en los archivos en lugar de hoy.
{ "packageName": "com.example.app", "month": "202608" }
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|---|
packageName | cadena | sí | Nombre de paquete de Android, p. ej. app.getpsst | |
month | cadena | no | Mes del informe como YYYYMM; por defecto el mes UTC actual. Se ignora cuando se dan startDate y endDate | |
installsDimension | uno de overview, country, language, device, os_version, carrier, app_version | no | "overview" | Qué informe de instalaciones leer. overview no está documentado por Google pero está presente en buckets reales; los otros son los desgloses documentados |
include | lista de uno de ratings, crashes, reviews | no | [] | Familias de informes adicionales a leer. Los archivos faltantes son normales: Google emite un informe solo cuando hay algo que informar |
storePerformanceDimension | uno de traffic_source, country | no | "traffic_source" | Qué desglose de rendimiento de tienda leer |
storePerformanceTotals | booleano | no | false | Leer la variante total_ en su lugar. Es un informe diferente, no un resumen del mismo: lleva solo adquisiciones, sin visitantes y sin tasa de conversión, y para algunas aplicaciones cubre muchos menos fechas y atribuye cada adquisición a una fuente de marcador de posición |
ratingsDimension | uno de country, language, device, os_version, carrier, app_version | no | "country" | Dimensión para el informe de calificaciones |
crashesDimension | uno de device, os_version, app_version | no | "app_version" | Dimensión para el informe de fallos |
startDate | cadena | no | Inicio de ventana en YYYY-MM-DD. Con endDate, lee cada mes que toca la ventana y filtra filas a ella | |
endDate | cadena | no | Fin de ventana en YYYY-MM-DD |
Establece SEO_MCP_PLAY_BUCKET al bucket de informes (gs://pubsite_prod_... y el nombre simple ambos funcionan) y SEO_MCP_PLAY_CREDENTIALS a una clave de cuenta de servicio con acceso de lectura a ese bucket, recurriendo a GOOGLE_APPLICATION_CREDENTIALS. El acceso de lectura al bucket es una concesión diferente de la invitación de Play Console que play_vitals necesita. month por defecto es el mes UTC actual.
app_store_listing
Lee una ficha de App Store a través de App Store Connect y mide los campos de cada locale contra los límites de Apple: nombre 30, subtítulo 30, palabras clave 100, texto promocional 170. Apple indexa solo el nombre, el subtítulo y el campo de palabras clave, por lo que la descripción se informa pero nunca se puntúa, y un campo que supera su límite por un carácter se elimina silenciosamente en lugar de rechazarse, por lo que cada campo se informa contra su límite. El texto promocional se destaca por separado porque es el único de estos que se puede cambiar en una versión en vivo sin revisión.
Una aplicación puede tener un registro en vivo y uno editable al mismo tiempo, por lo que state selecciona cuál se lee y el resultado indica el registro y la versión que usó. Cuando el registro que pediste no existe, se informa el otro y una nota lo dice en lugar de pasarlo como lo que pediste.
El estado informado proviene de appVersionState, recurriendo al obsoleto appStoreState. Los dos escriben lo mismo de manera diferente: una ficha en vivo lee READY_FOR_DISTRIBUTION donde el atributo obsoleto decía READY_FOR_SALE. La salida capturada antes y después de ese cambio diferirá solo en la cadena, sin que haya pasado nada con la ficha.
{ "bundleId": "com.example.app", "state": "live", "platform": "IOS", "storefronts": ["us", "gb"] }
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|---|
appId | cadena | no | ID de aplicación numérico de App Store Connect; proporciona este o bundleId | |
bundleId | cadena | no | ID de paquete, resuelto a un ID de aplicación cuando no se da appId; proporciona este o appId | |
platform | uno de IOS, MAC_OS, TV_OS, VISION_OS | no | "IOS" | Plataforma de App Store cuya versión se lee |
state | uno de live, editable | no | "live" | Leer la ficha en vivo o la editable que se prepara para el lanzamiento |
storefronts | lista de cadenas | no | ["us"] | Códigos de país de la tienda para la consulta pública de calificaciones |
Proporciona appId o bundleId. Establece SEO_MCP_ASC_KEY_PATH a la clave privada de .p8 y SEO_MCP_ASC_KEY_ID a su ID de clave, además de SEO_MCP_ASC_ISSUER_ID para una clave de equipo (las claves individuales no tienen ID de emisor). La clave y el token que firma nunca aparecen en la salida.
Una clave de equipo llega a todas las aplicaciones del equipo, por lo que una clave puede servirlas a todas. Lo que la limita es el rol que se le dio, y Apple no permite que el rol de una clave se cambie después: la única edición ofrecida es Revocar. Una clave de App Manager lee fichas pero no informes de Ventas y Tendencias ni de analítica, por lo que esos necesitan una clave separada creada con Admin, Finance o Sales and Reports en lugar de una actualización de la que tienes.
ratings es una lista, una entrada por tienda solicitada, no un objeto claveado por tienda:
{ "ratings": [{ "storefront": "us", "source": "itunes-lookup", "averageUserRating": 4.5, "userRatingCount": 12 }] }
La calificación de estrellas no proviene de App Store Connect. Su API no tiene ningún recurso de calificación agregada, solo calificaciones por edad, por lo que la calificación se lee de la búsqueda pública de tiendas de App Store, mientras que todos los demás campos de esta herramienta provienen de App Store Connect. Dos fuentes que informan un número que se ve igual de cualquier manera, por eso cada entrada lleva source. Una calificación de una página de tienda y una calificación de una API privada no son intercambiables y no deben compararse como si fueran la misma medición.
list_snapshots
Lista los documentos de instantánea que ya están en el directorio de instantáneas, del más reciente al más antiguo, con cuándo se tomó cada uno, la ventana que cubre y cuántas propiedades, aplicaciones, paquetes y complementos contiene.
{ "limit": 50 }
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|---|
limit | número | no | 50 | Máximo de instantáneas a devolver, del más reciente al más antiguo |
Un par de instantáneas no vale nada si nada puede decir qué archivos existen, y cada llamador de lo contrario mantenía su propio índice de un directorio que el servidor posee. Un archivo en el directorio que no es un documento de instantánea se lista con su error en lugar de ocultarse, para que un nombre que esperas encontrar nunca se lea silenciosamente como ausente. Un directorio faltante es una lista vacía, no un fallo: aún no se ha capturado nada. total y truncated se colocan junto a la lista porque la línea de comandos imprime solo la mitad estructurada, donde un corte de página en limit de otro modo se leería como el historial completo.
snapshot
Captura cuatro superficies en un solo documento con marca de tiempo: totales de Search Console y filas principales por propiedad, listados de App Store, instalaciones y tráfico de Google Play, y estadísticas de WordPress.org. Los datos de campo de Core Web Vitals, las estadísticas vitales de Android, las ventas de App Store y las reseñas de App Store no están incluidos; crux_field_data, play_vitals, app_store_sales y app_store_reviews leen esos. Esta es la herramienta para registrar un punto en una serie, porque ninguna de las consolas mantiene un historial que puedas comparar más tarde.
Los totales de Search Console provienen de la dimensión fecha, nunca sumando la dimensión de consulta. Google retiene consultas de bajo volumen, por lo que una suma a nivel de consulta subestima, y esa brecha se lee más tarde como una disminución que nunca ocurrió.
Una superficie que no se puede leer se registra en su lugar con su error y se nombra en surfacesWithErrors, nunca se omite, porque una superficie que desaparece silenciosamente se lee más tarde como una caída a cero. Una superficie lenta agota el tiempo de espera sin derribar el documento.
{
"properties": ["sc-domain:example.com"],
"apps": ["1234567890"],
"packages": ["com.example.app"],
"slugs": ["akismet"],
"windowDays": 28,
"outPath": "2026-09-03.json"
}
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|---|
properties | lista de cadenas | no | [] | Propiedades de Search Console a capturar |
apps | lista de cadenas | no | [] | Aplicaciones de App Store, cada una un id de aplicación numérico o un id de paquete |
packages | lista de cadenas | no | [] | Nombres de paquetes de Google Play |
slugs | lista de cadenas | no | [] | Slugs de complementos de WordPress.org |
windowDays | número | no | 28 | Ventana de Search Console en días, que termina hoy |
platform | uno de IOS, MAC_OS, TV_OS, VISION_OS | no | "IOS" | Plataforma de App Store para las superficies de aplicaciones |
storefronts | lista de cadenas | no | ["us"] | Códigos de país de tienda para calificaciones de App Store |
outPath | cadena | no | Nombre de archivo o ruta dentro del directorio de instantáneas (SEO_MCP_SNAPSHOT_DIR, predeterminado ~/.config/seo-mcp/snapshots); debe terminar en .json, o pasa auto para nombrar el archivo según el momento en que se tomó. Un archivo existente no se sobrescribe a menos que overwrite sea verdadero | |
overwrite | booleano | no | false | Reemplaza un archivo existente en outPath; sin esto, un archivo existente se deja intacto y se informa |
Pasa outPath para escribir el documento donde compare_snapshots pueda leerlo más tarde, o outPath: "auto" para que se nombre según el momento en que se tomó (2026-09-04T00-15Z.json), que es lo que hace que una ejecución desatendida produzca una serie en lugar de un archivo sobrescrito para siempre. Es un nombre de archivo dentro del directorio de instantáneas, SEO_MCP_SNAPSHOT_DIR o ~/.config/seo-mcp/snapshots por predeterminado; una ruta que se resuelve fuera de ese directorio o que no termina en .json se rechaza, y un archivo existente se deja en su lugar y se informa a menos que pases overwrite: true. Un modelo elige esta cadena, por lo que el directorio es el límite que evita que una llamada de herramienta trunque cualquier otra cosa en la máquina. La posición y el CTR son null en lugar de 0 cuando una ventana no tiene impresiones, para que una ventana vacía nunca se compare con datos reales como un colapso.
compare_snapshots
Lee dos documentos de instantánea e informa qué cambió entre ellos: clics, impresiones y posición por propiedad, cambios a nivel de página y de consulta por encima de un umbral de impresiones, deltas de instalaciones y calificaciones, cambios de versión y recuento de locales de App Store, longitudes de nombre, subtítulo, palabras clave, texto promocional y descripción por locale, además de qué campos cruzaron un límite de caracteres, fuentes de tráfico de Google Play por visitantes y adquisiciones, y el histograma de cinco estrellas de WordPress.org.
{ "from": "2026-08-06.json", "to": "2026-09-03.json", "minImpressions": 100 }
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|---|
from | cadena | sí | Nombre de archivo o ruta de instantánea dentro del directorio de instantáneas; latest nombra la instantánea más reciente en disco y previous la anterior | |
to | cadena | sí | Nombre de archivo o ruta de instantánea dentro del directorio de instantáneas; latest nombra la instantánea más reciente en disco y previous la anterior | |
minImpressions | número | no | 100 | Ignora movimientos de posición de página por debajo de este número de impresiones en ambos lados |
from y to se resuelven dentro del mismo directorio de instantáneas que snapshot's outPath, por lo que esta herramienta lee instantáneas y nada más. Cualquiera de los dos también acepta latest o previous en lugar de un nombre de archivo, que es la comparación que casi todos los llamadores realmente quieren y la única que pueden solicitar sin listar el directorio primero. Ambos omiten un archivo que no se analizará, y pedir previous con una sola instantánea en disco lo dice en lugar de comparar un documento consigo mismo.
Hace aritmética, nunca juicio. No te dirá si un cambio fue bueno o qué lo causó, porque un diff no puede respaldar esa afirmación. Una superficie que falló o falta en cualquiera de los lados se marca como no comparable y se nombra, para que un fallo de recopilación nunca se lea como un cambio, y un archivo que no es un documento de instantánea se rechaza en lugar de analizarse a medias.
Las instantáneas tomadas antes de que se capturara un campo aún se comparan. Un campo que un lado no lleva regresa como un delta null en lugar de como un cambio, y un par de aplicaciones sin longitudes por locale en ninguno de los lados informa localesComparable: false en lugar de un listado vaciado a cero caracteres.
app_store_reviews
Lee reseñas de clientes de App Store y tus respuestas, filtradas por calificación de estrellas o tienda, siguiendo el cursor de paginación propio de Apple.
{ "bundleId": "com.example.app", "rating": [1, 2], "territory": "USA", "limit": 100 }
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|---|
appId | cadena | no | Id de aplicación numérico de App Store Connect; proporciona esto o bundleId | |
bundleId | cadena | no | Id de paquete; proporciona esto o appId | |
rating | lista de números | no | Solo estas calificaciones de estrellas | |
territory | cadena | no | Solo reseñas de esta tienda | |
sort | uno de -createdDate, createdDate, rating, -rating | no | "-createdDate" | Orden de clasificación; más recientes primero por predeterminado |
limit | número | no | 100 | Máximo de reseñas a devolver entre páginas |
maxPages | número | no | 5 | Máximo de páginas a seguir |
Informa meanOfFetched y histogramOfFetched, nunca "la calificación". Esos describen solo las reseñas que devolvió esta llamada, y una página filtrada o truncada haría que un promedio fuera un número diferente con el mismo nombre. App Store Connect no expone ningún recurso de calificación agregada, lo cual es verificable en la propia especificación OpenAPI de Apple: cada ruta que coincide con "rating" es una calificación de edad.
app_store_discovery
Lee las superficies de App Store más allá del texto del listado: palabras clave de búsqueda (la lista real de palabras clave indexadas de Apple, mantenida por locale), etiquetas de aplicación, experimentos de optimización de página de producto, páginas de producto personalizadas, eventos dentro de la aplicación, disponibilidad territorial y resúmenes de reseñas.
{ "bundleId": "com.example.app", "locales": ["en-US", "ar-SA"], "platform": "IOS" }
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|---|
appId | cadena | no | Id de aplicación numérico de App Store Connect; proporciona esto o bundleId | |
bundleId | cadena | no | Id de paquete; proporciona esto o appId | |
include | lista de uno de searchKeywords, appTags, experiments, customProductPages, appEvents, availability, reviewSummarizations | no | [] | Qué superficies de descubrimiento leer; vacío lee todas |
limit | número | no | 50 | Filas por recurso |
locales | lista de cadenas | no | ["en-US"] | Locales para recursos por locale como searchKeywords |
platform | uno de IOS, MAC_OS, TV_OS, VISION_OS | no | "IOS" | Plataforma para recursos que la requieren |
includeRows | booleano | no | false | Incluye cada fila cruda además de los recuentos; desactivado por predeterminado para que una llamada de resumen se mantenga pequeña |
Cada recurso lleva sus propios parámetros obligatorios: searchKeywords necesita tanto un filtro de plataforma como de locale, appAvailabilityV2 es una relación de uno a uno que rechaza limit por completo. Un recurso que esta clave o aplicación no puede servir se informa como available: false, nunca como una lista vacía, porque "sin experimentos" y "no se pueden leer experimentos" son respuestas diferentes.
crux_field_data
Core Web Vitals de usuarios reales para un origen o una URL única del Chrome UX Report: el registro de campo actual de 28 días, con p75 y contenedores de histograma completos.
{ "origin": "https://example.com", "formFactor": "PHONE" }
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|---|
origin | cadena | no | Origen como https://example.com; agrega cada página debajo de él. Proporciona origin o url, no ambos | |
url | cadena | no | Una URL de página única. Proporciona origin o url, no ambos | |
formFactor | uno de PHONE, TABLET, DESKTOP | no | Clase de dispositivo; omite para todos los factores de forma combinados | |
metrics | lista de cadenas | no | Nombres de métricas a solicitar; omite para todas las disponibles |
Esto son datos de campo, no una prueba de laboratorio; guarda pagespeed para auditorías de Lighthouse. Google está descontinuando los propios datos del mundo real de PageSpeed, así que aquí es donde se mueven las mediciones de campo. Un origen con muy pocas muestras anonimizadas devuelve hasData: false con una nota en lugar de un error o ceros, ya que un LCP a cero se leería como una regresión catastrófica.
crux_history
Las mismas métricas de campo como una serie semanal, aproximadamente seis meses de historial.
{ "origin": "https://example.com", "formFactor": "PHONE", "collectionPeriodCount": 25 }
| Parámetro | Tipo | Requerido | Predeterminado | Descripción |
|---|---|---|---|---|
origin | string | no | Origen como https://example.com; agrega cada página bajo él. Proporcione origin o url, no ambos | |
url | string | no | Una URL de página individual. Proporcione origin o url, no ambos | |
formFactor | uno de PHONE, TABLET, DESKTOP | no | Clase de dispositivo; omita para combinar todos los factores de forma | |
metrics | lista de string | no | Nombres de métricas a solicitar; omita para todos los disponibles | |
collectionPeriodCount | number | no | Períodos semanales a devolver, de 1 a 40. El historial documentado es de unos seis meses; la API decide lo que realmente tiene |
Cada período es una ventana móvil de 28 días con pasos semanales, por lo que los puntos consecutivos se superponen por tres semanas y un movimiento de una semana a otra no es un cambio independiente. Los períodos con muy pocas muestras mantienen su lugar en la serie como null en lugar de eliminarse, por lo que los valores permanecen alineados con collectionPeriods.
app_store_sales
Lee App Store Sales and Trends: unidades descargadas por día, por territorio, por aplicación, resumidas por SKU.
{ "reportDate": "2026-08-30", "frequency": "DAILY", "reportType": "SALES", "reportSubType": "SUMMARY" }
| Parámetro | Tipo | Requerido | Predeterminado | Descripción |
|---|---|---|---|---|
reportDate | string | no | Fecha del informe. DAILY y WEEKLY toman YYYY-MM-DD (WEEKLY significa la fecha de finalización de la semana), MONTHLY toma YYYY-MM, YEARLY toma YYYY. El valor predeterminado es el período completo más reciente para la frecuencia | |
frequency | uno de DAILY, WEEKLY, MONTHLY, YEARLY | no | "DAILY" | Período del informe |
reportType | uno de SALES, PRE_ORDER, SUBSCRIPTION, SUBSCRIPTION_EVENT, SUBSCRIBER, INSTALLS, FIRST_ANNUAL | no | "SALES" | Tipo de informe de Sales and Trends |
reportSubType | uno de SUMMARY, DETAILED, SUMMARY_INSTALL_TYPE, SUMMARY_TERRITORY, SUMMARY_CHANNEL | no | "SUMMARY" | Subtipo de informe |
version | string | no | Versión del informe, como 1_0 o 1_3, cuando el valor predeterminado no se acepta | |
includeRows | boolean | no | false | Incluir cada fila de informe sin procesar, así como el resumen por SKU |
Establezca SEO_MCP_ASC_VENDOR_NUMBER; App Store Connect muestra el número de proveedor en Payments and Financial Reports, junto al nombre de la entidad legal. Sales and Trends necesita una clave de equipo con el rol Admin, Finance o Sales and Reports. Los informes diarios llegan al día siguiente, por lo que la fecha de informe predeterminada es dos días atrás en lugar de hoy. reportDate toma la forma que su frecuencia necesita: YYYY-MM-DD para DAILY y para WEEKLY, donde significa el domingo de finalización de la semana, YYYY-MM para MONTHLY, y YYYY para YEARLY; déjelo fuera y cada frecuencia usa su período completo más reciente.
Un período sin ventas devuelve hasData: false con una nota, no un error, porque un día tranquilo no debería parecer una integración rota. Las unidades provienen de la canalización de Sales and Trends, que es separada de App Analytics y puede diferir de ella.
play_vitals
Lee Android vitals de la API Play Developer Reporting: tasa de fallos, tasa de ANR, conteos de errores y métricas de inicio, diarias o por hora, con desgloses opcionales como versionCode o countryCode.
{ "packageName": "com.example.app", "metricSets": ["crashRate", "anrRate"], "days": 28 }
| Parámetro | Tipo | Requerido | Predeterminado | Descripción |
|---|---|---|---|---|
packageName | string | sí | Nombre del paquete de Android | |
metricSets | lista de uno de crashRate, anrRate, errorCount, slowStartRate, excessiveWakeupRate | no | ["crashRate","anrRate"] | Qué conjuntos de métricas de Android vitals consultar |
aggregationPeriod | uno de DAILY, HOURLY | no | "DAILY" | DAILY se informa en America/Los_Angeles, HOURLY en UTC |
days | number | no | 28 | Cuántos días atrás consultar |
dimensions | lista de string | no | [] | Dimensiones de desglose como versionCode o countryCode |
pageSize | number | no | 1000 | Filas por conjunto de métricas |
includeRows | boolean | no | false | Incluir cada fila sin procesar, así como los conteos; desactivado por defecto para que una llamada de resumen siga siendo pequeña |
Establezca SEO_MCP_PLAY_CREDENTIALS a la clave de cuenta de servicio, con respaldo a GOOGLE_APPLICATION_CREDENTIALS. La cuenta también debe ser invitada en Play Console bajo Users and permissions con el permiso para ver información de la aplicación y calidad de la aplicación. El token se acuña para el alcance playdeveloperreporting, que es una concesión separada de la lectura de Cloud Storage que play_store_stats necesita. Una cuenta puede tener ambos, pero una clave que solo tiene la concesión del bucket recibe un 403 aquí.
La ventana se limita a la frescura que la API informa por sí misma, ya que rechaza una fecha de finalización más allá de eso y preguntar hasta hoy siempre falla. El resultado dice cuán actuales son realmente los datos, por lo que cero filas hasta una fecha conocida se distingue de cero filas porque el día aún no ha llegado. Esta API no lleva datos de adquisición o conversión; play_store_stats tiene eso.
Google Ads
Lee la cuenta a través de la API en lugar de la consola. Una tabla de consola pagina, por lo que un conteo tomado de la primera pantalla puede ser incorrecto sin parecerlo: un conteo de palabras clave se leyó como dos cuando la respuesta era cinco, porque la tabla muestra diez filas y había catorce. Estas herramientas devuelven cada fila.
Establezca GOOGLE_ADS_DEVELOPER_TOKEN, GOOGLE_ADS_CLIENT_ID, GOOGLE_ADS_CLIENT_SECRET, GOOGLE_ADS_REFRESH_TOKEN y GOOGLE_ADS_CUSTOMER_ID (guiones opcionales). Dos conveniencias: GOOGLE_ADS_CLIENT_SECRET_PATH lee el id de cliente y el secreto del JSON de cliente OAuth que Google Cloud le da, y GOOGLE_ADS_ENV_FILE apunta a un archivo existente con forma de .env que contiene cualquiera de estos, por lo que un token de actualización que ya vive en algún lugar se lee en su lugar en lugar de copiarse. El entorno del proceso gana sobre el archivo. GOOGLE_ADS_API_VERSION anula la versión de la API.
ads_campaigns
Nombre de campaña, estado, presupuesto diario, impresiones, clics, costo y conversiones en una ventana.
{ "days": 30 }
| Parámetro | Tipo | Requerido | Predeterminado | Descripción |
|---|---|---|---|---|
days | number | no | 30 | Cuántos días atrás informar, terminando hoy |
ads_keywords
Cada palabra clave con su estado, oferta de CPC efectiva, estado de aprobación, estado de publicación y métricas.
ELIGIBLE no significa publicación. Significa aprobada y capaz de publicarse, y una palabra clave en pausa lo informa. Por eso status se devuelve junto a él: sin él, la fila de una palabra clave en pausa es idéntica a una activa, y alguien que acaba de pausar tres palabras clave lee eso como que la pausa no se aplicó. Las palabras clave en pausa se nombran en una nota en lugar de eliminarse, porque eliminar filas silenciosamente es el mismo fallo un nivel más abajo: preguntas si una palabra clave está en la cuenta y no obtienes nada. Pase status para filtrar deliberadamente.
{ "days": 30 }
| Parámetro | Tipo | Requerido | Predeterminado | Descripción |
|---|---|---|---|---|
days | number | no | 30 | Cuántos días atrás informar, terminando hoy |
status | uno de ENABLED, PAUSED, REMOVED | no | Limitar a un estado de palabra clave. Omitido, cada palabra clave se devuelve con su estado nombrado, porque eliminar filas silenciosamente es cómo un conteo tomado de esta herramienta sale mal como lo hace un conteo de consola |
ads_ads
Cada anuncio con su fortaleza de anuncio, estado de aprobación de política, estado de publicación y métricas.
{ "days": 30 }
| Parámetro | Tipo | Requerido | Predeterminado | Descripción |
|---|---|---|---|---|
days | number | no | 30 | Cuántos días atrás informar, terminando hoy |
ads_ad_copy
Lee lo que un anuncio realmente dice. ads_ads da el id, la fortaleza, la aprobación y el estado; esto da el texto, que es lo que toda pregunta creativa necesita y la razón por la que esa pregunta de otro modo termina en el navegador.
{ "adGroup": "brand-exact" }
| Parámetro | Tipo | Requerido | Predeterminado | Descripción |
|---|---|---|---|---|
adGroup | string | no | Limitar a un grupo de anuncios por nombre. Omitido, cada anuncio en la cuenta se lee, que es lo que responde si un titular se repite entre grupos de anuncios | |
adId | string | no | Limitar a un anuncio por su id numérico, para leer de vuelta el texto que se suponía que debía enviarse | |
includeRemoved | boolean | no | false | Incluir anuncios eliminados. Desactivado por defecto: el texto de un anuncio eliminado es historia y desplaza a los anuncios que se están publicando |
Cada titular y descripción vuelve con su fijación y la etiqueta de rendimiento propia de Google, más la ruta de visualización, las URL finales y los temas de política detrás de un estado limitado o desaprobado. La palabra de aprobación dice que algo está mal; el tema dice qué. APPROVED_LIMITED junto a TRADEMARKS_IN_AD_TEXT es una solución; APPROVED_LIMITED por sí solo es un viaje a la consola.
También responde las dos preguntas que una vista por anuncio no puede:
- Por qué la fortaleza es Poor. El conteo contra lo que Google quiere,
3 of 15 headlines, 2 of 4 descriptions, y cuántos activos están fijados. La fijación suele ser deliberada, suele ser invisible en la palabra de fortaleza, y es una razón común por la que la fortaleza se lee más baja de lo que el texto merece. El texto repetido dentro de un anuncio también se nombra, ya que un activo repetido ocupa un espacio sin agregar una variación. - Si un titular está duplicado entre anuncios. El texto de titular que aparece en más de un anuncio se lista con los anuncios y grupos de anuncios que lo llevan. Dos anuncios en un grupo de anuncios que comparten sus titulares no son dos variantes probándose entre sí, y nada en la consola lo dice de un vistazo.
Los anuncios eliminados se excluyen a menos que includeRemoved esté establecido, y solo un anuncio de búsqueda responsivo lleva texto en estos campos: cualquier otro tipo de anuncio se lista con su tipo y sin texto, en lugar de como un anuncio sin nada que decir. Los activos adjuntos al anuncio, campaña o cuenta, como enlaces de sitio, destacados y precios, no se leen aquí, por lo que un anuncio que parece delgado en esta salida puede seguir publicándose con activos junto a él.
ads_assets
Lo que está adjunto bajo el anuncio: enlaces de sitio, destacados, fragmentos estructurados, promociones, precios, activos de llamada e imagen, en los tres niveles, con lo que cada uno realmente dice en lugar de solo su tipo e id.
{ "campaign": "search-uk-us-2026-09" }
| Parámetro | Tipo | Requerido | Predeterminado | Descripción |
|---|---|---|---|---|
campaign | string | no | Limitar activos de campaña y grupo de anuncios a una campaña por nombre. Los activos a nivel de cuenta aún se listan, porque se aplican a cada campaña incluida esta | |
type | uno de SITELINK, CALLOUT, STRUCTURED_SNIPPET, PROMOTION, PRICE, CALL, IMAGE | no | Limitar a un tipo de activo. Omitido, cada tipo se lista, incluidos los tipos para los que esta herramienta no tiene lectura estructurada | |
includeRemoved | boolean | no | false | Incluir enlaces cuyo estado es eliminado. Desactivado por defecto: un activo eliminado es historia y desplaza a los que pueden publicarse |
Una promoción se lee de vuelta como up to 20% off on Pro plan with code LAUNCH20, 2026-01-01 to 2026-01-31, no como PROMOTION #4417. Eso importa porque Google declara el porcentaje de una promoción en millonésimas, donde 1,000,000 es 100%, por lo que el campo sin procesar es un número que nadie reconocería como descuento. Los precios vuelven con sus ofertas y moneda, los enlaces de sitio con sus descripciones.
Tres cosas que vale la pena saber antes de leer un resultado:
- Un recurso a nivel de cuenta aplica a cada campaña, por lo que aparece listado incluso cuando nombras una sola campaña. Esta es la otra mitad de
ads_ad_copy: un anuncio que parece vacío allí puede estar sirviéndose con cuatro enlaces de sitio y una promoción a su lado, ninguno de los cuales está adjunto a su campaña. - Adjunto no se muestra. Google decide en cada subasta si mostrar un recurso y cuáles. Esto indica lo que está disponible para servirse, no lo que se sirvió.
- Un nombre de campaña que no coincide con nada se rechaza, no se responde. Un error tipográfico solía devolver cero filas sin error, junto a una nota que explicaba que los recursos a nivel de cuenta también se listan, por lo que el lector concluía que la cuenta no tenía ninguno. El nombre se resuelve antes de leer cualquier cosa, y uno desconocido lo dice. Quien escribe mal una campaña es exactamente quien luego dice "esa campaña no tiene enlaces de sitio" y actúa en consecuencia.
- Un nivel que no se puede leer se reporta como un error en su lugar. Los tres niveles son tres consultas separadas, y si una falla, las otras dos aún regresan con
levelErrorsnombrando la que no lo hizo. Una lista vacía que silenciosamente significara "la consulta falló" se leería como "nada adjunto", que es la respuesta incorrecta a la única pregunta que se le hace a esta herramienta.
Un tipo para el que esta herramienta no tiene una lectura estructurada se nombra con su tipo de recurso y su tipo de campo, y se deja así, en lugar de dar un resumen inventado: un recurso TEXT archivado como BUSINESS_NAME se describe principalmente por la segunda mitad. El tipo de campo se muestra solo cuando difiere del tipo de recurso, ya que normalmente son la misma palabra y repetirla es ruido. Las métricas de recursos no se reportan aquí.
ads_query
Una consulta GAQL SELECT arbitraria para una pregunta que las lecturas estructuradas no cubren. GAQL no tiene otra declaración que SELECT, por lo que esto no puede cambiar nada, y una consulta que no comience con SELECT se rechaza.
{ "query": "SELECT campaign.name, metrics.cost_micros FROM campaign WHERE segments.date DURING LAST_7_DAYS" }
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|---|
query | string | sí | Una declaración SELECT de GAQL. GAQL no tiene otra declaración, por lo que esto no puede cambiar nada |
ads_search_terms
Las consultas que realmente activaron un anuncio, con la palabra clave que coincidió con cada una. Este es el equivalente de pago de la dimensión de consultas de Search Console, y conlleva la misma advertencia: Google retiene términos que muy pocas personas buscaron, por lo que un término que no aparece listado es desconocido, no ausente.
{ "days": 90, "minImpressions": 1 }
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|---|
days | number | no | 30 | Cuántos días hacia atrás reportar, terminando hoy |
minCost | number | no | 0 | Eliminar términos de búsqueda que cuesten menos que esto durante el período |
minImpressions | number | no | 0 | Eliminar términos de búsqueda por debajo de este número de impresiones |
zeroConversionsOnly | boolean | no | false | Conservar solo términos que no convirtieron nada, que es la lista que alimenta las palabras clave negativas |
ads_changes
Qué cambió en la cuenta, cuándo, qué campos, quién, y si vino de una herramienta o de alguien en el navegador: client es GOOGLE_ADS_API para lo primero y GOOGLE_ADS_WEB_CLIENT para lo segundo. Este es el rastro de auditoría para cualquier cosa que ads_update escriba, y para ediciones de consola hechas a mano. Google conserva 30 días, por lo que un período más largo se rechaza en lugar de truncarse silenciosamente.
{ "days": 14, "limit": 100 }
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|---|
days | number | no | 14 | Cuántos días de historial de cambios leer, terminando ahora. Google conserva 30 días y rechaza más |
limit | number | no | 100 | Los cambios más recientes a devolver |
ads_negatives
Las palabras clave negativas ya existentes, a nivel de campaña, grupo de anuncios o conjunto compartido. Un negativo bloquea tráfico sin dejar registro de que lo hizo, por lo que esta es la lista a consultar cuando una palabra clave deja de servirse y nada parece estar mal, y antes de agregar un término que quizás ya esté allí.
{ "level": "all" }
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|---|
level | uno de campaign, adGroup, sharedSet, all | no | "all" | Qué negativos leer. Un término bloqueado a nivel de campaña está bloqueado en toda ella; un conjunto compartido aplica a cada campaña a la que esté adjunto |
ads_negatives_update
Agrega o elimina palabras clave negativas en un lote, enumeradas una por una. No hay forma de patrón ni de coincidir con todo a propósito: "bloquear todo término que coincida con X" está a un error tipográfico de un error del tamaño de una cuenta, y una lista explícita no puede cometer ese error.
{ "action": "add", "level": "campaign", "target": "search-uk-us", "keywords": ["free", "crack"], "matchType": "EXACT", "dryRun": false }
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|---|
action | uno de add, remove | sí | Agregar palabras clave negativas o eliminar las existentes. La eliminación importa tanto como la adición: un negativo incorrecto se manifiesta como nada en absoluto | |
level | uno de campaign, adGroup | no | "campaign" | Dónde viven los negativos. Un negativo a nivel de campaña bloquea el término en toda esa campaña |
target | string | sí | El nombre de la campaña o grupo de anuncios. Debe coincidir exactamente con uno o no se cambia nada | |
keywords | lista de string | sí | Los términos negativos, enumerados uno por uno. No hay forma de patrón ni de coincidir con todo: un selector está a un error tipográfico de bloquear una campaña entera | |
matchType | uno de BROAD, PHRASE, EXACT | no | "EXACT" | Cómo bloquea cada término. BROAD bloquea cualquier consulta que contenga todas sus palabras, que es la configuración que puede matar silenciosamente una campaña |
dryRun | boolean | no | true | Reportar qué cambiaría, y qué negativos propuestos bloquearían una palabra clave activa, sin cambiar nada |
confirm | boolean | no | false | Ejecutar el lote aunque una protección se active. La ejecución de prueba lista qué se activó, por lo que esto confirma algo ya leído |
Los negativos parecen seguros porque solo reducen el gasto, y ese instinto es lo que los hace peligrosos. Una oferta incorrecta se manifiesta como gasto. Un negativo incorrecto se manifiesta como nada: el tráfico deja de llegar, el término sale del informe de términos de búsqueda, y ninguna fila en ningún lugar dice por qué. Agregar backup como negativo amplio a una campaña de plugins de respaldo termina su tráfico, y Google no reporta error porque es un negativo perfectamente válido.
Entonces, antes de agregar cualquier cosa, cada negativo propuesto se verifica contra las palabras clave activas de la propia campaña, y el lote se rechaza a menos que confirm esté configurado. El rechazo nombra lo que habría costado: "backup" as a BROAD negative would block this campaign's own keyword "wordpress backup", which served 41 impressions. La verificación refleja de cerca la coincidencia de Google, pero Google es la autoridad, y es deliberadamente generosa, porque una advertencia falsa cuesta una frase y una omitida cuesta la campaña.
Las eliminaciones no se verifican por colisiones. Eliminar un negativo solo puede dejar pasar tráfico, lo que se manifiesta como gasto en lugar de silencio.
ads_update
Cambia una oferta de palabra clave, presupuesto diario de campaña, estado de campaña, estado de anuncio o estado de palabra clave. Esta es la única herramienta aquí que gasta dinero, por lo que está diseñada para ser difícil de disparar por accidente.
Pausar una palabra clave es su propio tipo, porque bajar su oferta no es lo mismo. Una palabra clave con oferta reducida sigue habilitada, sigue siendo elegible y sigue compitiendo por el mismo presupuesto diario. Si la razón para actuar era que el presupuesto es la restricción, bajar la oferta no libera nada de él.
{ "kind": "budget", "target": "search-uk-us-2026-09", "value": "5.00", "dryRun": false, "confirm": true }
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|---|
kind | uno de bid, budget, campaignStatus, adStatus, keywordStatus | sí | Qué cambiar: la oferta de CPC máximo de una palabra clave, el presupuesto diario de una campaña, el estado de una campaña, el estado de un anuncio o el estado de una palabra clave. Usa keywordStatus para detener el servicio de una palabra clave; bajar su oferta no es lo mismo, porque la palabra clave sigue siendo elegible y sigue compitiendo por el mismo presupuesto | |
target | string | sí | El texto de la palabra clave, el nombre de la campaña o el id numérico del anuncio. Debe coincidir exactamente con una cosa o la llamada se rechaza | |
value | string | sí | El nuevo monto en dólares para una oferta o presupuesto, o pause o enable para un estado | |
dryRun | boolean | no | true | Reportar qué cambiaría y qué protecciones activa, sin cambiar nada. Activado por defecto: esta herramienta gasta dinero, por lo que realizar un cambio debe solicitarse |
confirm | boolean | no | false | Realizar un cambio que active una protección. Se ignora en una ejecución de prueba. La ejecución de prueba lista las razones de las protecciones, por lo que esto confirma algo ya leído en lugar de algo no visto |
Cuatro rieles, cada uno de un fallo real en lugar de uno hipotético:
- Una ejecución de prueba por defecto.
dryRunes verdadero por defecto, por lo que omitirlo reporta el cambio y se detiene. Un parámetro obligatorio lo impone mejor que una bandera de línea de comandos, porque una bandera puede olvidarse y un valor predeterminado no. - Exactamente una coincidencia o rechazo. Un objetivo que no coincide con nada es un error tipográfico; un objetivo que coincide con dos es una solicitud para cambiar algo que no nombraste. Ambos se detienen antes de cualquier escritura.
- Protecciones con razones, en palabras. Más de tres veces el monto actual, más de $25 en una sola oferta o presupuesto diario, o pausar algo que actualmente se está sirviendo. Un cambio de presupuesto también indica el equivalente mensual, porque $30 al día parece poco y son unos $912 al mes. La ejecución de prueba lista las razones, y
confirmluego confirma algo que has leído en lugar de algo no visto. - El valor se lee de nuevo después de la escritura. Un HTTP 200 significa que la solicitud fue aceptada, no que almacenó lo que querías decir. El resultado lleva
readBackymatches, y una discrepancia se devuelve como error.
Un cambio que sería una no-operación lo dice en lugar de enviar una mutación sin sentido.
Desde la línea de comandos, esta herramienta necesita --allow-spend además de --allow-write. Una bandera que autorice tanto "reenviar un sitemap" como "triplicar un presupuesto diario" no es una barrera.
ads_keyword_create
Agrega una palabra clave a un grupo de anuncios. La única herramienta aquí que crea en lugar de cambiar, y está protegida de manera diferente por esa razón.
{ "keyword": "wordpress backup plugin", "adGroup": "brand-exact", "bid": 1.2, "dryRun": false }
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|---|
keyword | string | sí | El texto de la palabra clave a agregar. Se crea tal como está escrito; esta herramienta no adivina variantes | |
adGroup | string | sí | El grupo de anuncios al que agregarla. Debe coincidir exactamente con uno o no se agrega nada | |
bid | number | sí | La oferta de CPC máximo en dólares. No hay oferta actual con la que comparar en una creación, por lo que la única verificación de tamaño es el techo | |
matchType | uno de EXACT, PHRASE, BROAD | no | "EXACT" | Cómo coincide la palabra clave. EXACT por defecto porque es la que compra lo que dice; PHRASE y BROAD compran más que el texto escrito aquí y cada una activa una protección |
dryRun | boolean | no | true | Reportar qué se agregaría y qué protecciones activa, sin agregar nada |
confirm | boolean | no | false | Agregarla aunque una protección se active. La ejecución de prueba lista cada razón, por lo que esto confirma algo ya leído |
Cada otra escritura en este paquete lee un valor actual, lo compara con el solicitado y se niega cuando ya coinciden. Una creación no tiene valor actual. No hay nada que comparar ni contra qué negarse, por lo que la comparación debe reemplazarse en lugar de omitirse, y lo que la reemplaza es una verificación de duplicados:
- Se rechaza una palabra clave que ya existe en el grupo de anuncios objetivo, incluidas las eliminadas. Un criterio eliminado aún conserva el texto, y Google rechaza el duplicado con un error que menciona un recurso que la interfaz no muestra, lo cual resulta confuso de encontrar sin previo aviso.
- Una copia en otro lugar de la cuenta activa una protección en lugar de rechazarse. Ejecutar el mismo texto en dos grupos de anuncios puede ser intencional, por lo que rechazarlo haría imposible una estructura legítima; no decir nada permitiría que dos copias compitieran silenciosamente por un mismo presupuesto.
EXACTde forma predeterminada.PHRASEyBROADcompran cada una más que el texto escrito aquí, por lo que cada una activa una protección. Amplia es el tipo de concordancia que gasta en búsquedas que nadie tenía intención de comprar.- La palabra clave se lee de vuelta después de la escritura, y su tipo de concordancia y estado se comparan con lo enviado. Un 200 en una creación significa aceptada, no presente y correcta.
Una asimetría que vale la pena señalar claramente: una palabra clave creada comienza a servir de inmediato, y a diferencia de un cambio de oferta, no hay estado anterior al que volver. Deshacerla significa pausar o eliminar lo que se creó.
ads_update_batch
Cambia varias ofertas de palabras clave, o varios presupuestos diarios de campañas, en una sola llamada. Un tipo por llamada: un total entre ofertas y presupuestos sumaría un límite por clic a una cantidad por día, y ninguna frase honesta describe esa suma.
{ "kind": "bid", "changes": [{ "target": "wordpress backup", "value": 0.85 }, { "target": "backup plugin", "value": 0.6 }], "dryRun": false, "confirm": true }
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|---|
kind | uno de oferta, presupuesto | sí | Un tipo por llamada. Una protección de suma solo es honesta dentro de un tipo: las ofertas y los presupuestos suman dólares, los estados no, y mezclarlos hace que el total sea ilegible | |
changes | lista JSON | sí | Una lista nombrada de pares, cada uno con su propio valor. No hay forma de selector: la enumeración no puede cometer el error que un patrón sí puede | |
dryRun | booleano | no | true | Resolver y valorar cada entrada e informar el total, sin cambiar nada |
confirm | booleano | no | false | Ejecutar el lote aunque se haya activado una protección. La ejecución de prueba enumera cada motivo, por lo que esto confirma algo ya leído |
Es una lista nombrada de pares, no una regla aplicada a muchas cosas. No hay "subir todo un 20%" ni selector, porque el error que esta herramienta existe para prevenir es exactamente el que un selector facilita: un patrón que coincide con más de lo que el llamador imaginó, aplicado antes de que nadie pueda ver la lista que produjo. Cada entrada nombra un objetivo y el valor al que debe terminar, y la ejecución de prueba imprime esa lista de vuelta.
Cuatro cosas que hace que ads_update llamado en un bucle no hace:
- Todo se resuelve antes de que se escriba nada. Si la entrada cuatro no coincide con nada, las entradas uno a tres no están ya activas. Un bucle de llamadas individuales falla a mitad de camino y deja la cuenta en un estado que nadie eligió, sin una sola fila en ningún lugar que lo indique.
- La suma está protegida, no solo cada entrada. Cinco aumentos que están cada uno dentro de los límites por elemento siguen siendo un cambio de gasto grande en conjunto, y hacerlos uno a la vez es como eso pasa desapercibido.
- Se nombra la entrada que se desvía del resto. Diecinueve ofertas que se mueven unos centavos y una que se mueve $40 pueden estar bajo todos los límites y seguir siendo el error. Una entrada cuyo movimiento es mucho mayor que la mediana del lote se marca por nombre, porque un error tipográfico se esconde dentro de un total aceptable, y eso es exactamente cómo un lote difiere de las mismas escrituras enviadas una a la vez.
- Dos entradas no pueden nombrar lo mismo. El mismo objetivo dos veces se rechaza, y también dos campañas con nombres diferentes que comparten un presupuesto, donde el total lo contaría dos veces y la segunda escritura ganaría silenciosamente.
Los límites por entrada permanecen planos sin importar cuán larga sea la lista, porque la pregunta por entrada es si esa entrada es un error tipográfico, y un error tipográfico no se vuelve más aceptable en un lote más grande. El límite en el total del lote crece con el lote, lentamente: veinte entradas no son veinte veces el riesgo de una, es una decisión tomada una vez. Una protección que se activa en cada lote realista no es una protección, es una casilla de verificación, y una vez que confirm es rutinario, se pasa sin leer.
El total siempre se indica en palabras, ya sea que algo se active o no, porque la frase es lo que se lee y la protección es solo lo que te detiene cuando no lo hace. Para presupuestos, esa es la cifra mensual en ambos sentidos: These daily budgets come to $13.00 a day, about $395 a month, up from $10.00 a day, about $304 a month.
Cada valor se lee de vuelta después de la escritura, entrada por entrada. El resultado nombra qué entradas no almacenaron lo enviado primero, luego cuáles están activas con lo que la cuenta tiene ahora, porque en un aterrizaje parcial la pregunta nunca es cuántas sino cuáles. Una solicitud aceptada es una aceptación, no N valores almacenados, y un lote es exactamente donde se esconde un aterrizaje parcial. El lote se envía como una sola solicitud sin fallo parcial, por lo que una escritura rechazada no deja nada atrás, y el error lo dice en lugar de dejarte adivinar.
Como ads_update, necesita --allow-spend además de --allow-write desde la línea de comandos.
Ejecutar una herramienta desde la línea de comandos
Cada herramienta anterior también se puede ejecutar sin un cliente MCP, que es lo que se usa cuando un resultado debe aterrizar en un archivo que una ejecución posterior pueda comparar:
seo-mcp query search_analytics --site-url sc-domain:example.com --start-date 2026-08-05 --out /tmp/sa.json
seo-mcp query --help # list the tools
seo-mcp query wporg_plugin --help # list one tool's parameters
Cada ejecución nombra su propia versión en stderr (seo-console-mcp 0.15.1 running wporg_plugin), para que stdout permanezca analizable y un archivo --out permanezca como JSON puro. Un resultado no dice de otro modo qué binario lo produjo, y eso no es académico: npx reutilizará una compilación anterior en caché sin ningún error, y una instalación fallida deja la versión anterior en su lugar y funcionando. Lo que se está ejecutando, a lo que resuelve el rango de versiones y lo que npm llama último son tres valores que generalmente coinciden e independientemente no tienen que hacerlo.
Las banderas son los nombres de parámetros de la herramienta en kebab-case (--site-url para siteUrl); la ortografía camelCase también funciona. Los valores de lista están separados por comas. El resultado se escribe en --out, o en stdout cuando se omite, y un fallo sale con código distinto de cero con el mensaje en stderr. Ejecuta la misma implementación que expone la superficie MCP, por lo que los dos no pueden divergir.
Las herramientas que cambian datos (submit_sitemap, delete_sitemap, request_recrawl, indexnow_submit) están marcadas como (write) en el listado y se niegan a ejecutarse desde la línea de comandos a menos que se pase --allow-write.
Un historial está a una línea de cron de distancia, y el servidor deliberadamente no posee un programador: tu máquina ya tiene uno que sobrevive a un reinicio.
# every Monday at 06:00, one snapshot named after the moment it was taken
0 6 * * 1 seo-mcp query snapshot --properties sc-domain:example.com --out-path auto
Desarrollo
npm run dev
npm run build
npm test
npm run lint
npm run format
npm run format:check
npx tsc --noEmit
Las pruebas usan clientes falsos de Google inyectados y nunca llaman a servicios reales de Google. No confirmes claves de cuentas de servicio. Además de *.key.json, este repositorio ignora credentials*.json, .env*, archivos PEM y archivos P12.