Changelog
[Unreleased]
Section titled “[Unreleased]”schema_validateparcourt les nœuds JSON-LD@graph, y compris dans un tableau racine, sans compter le conteneur non typé comme un schéma valide.applicationCategoryetoperatingSystemsont signalés comme propriétés recommandées deSoftwareApplication, sans invalider leur absence. La réponse distingue explicitement la présence de champs vérifiée localement de l’éligibilité Google aux résultats enrichis, non évaluée.heading_auditne signale plusTL;DRcomme un titre vide.
Documentation
Section titled “Documentation”- Ajout d’un guide d’installation central, d’un guide Bing et de prompts par fournisseur avec des limites explicites sur l’indexation, les preuves et les mutations.
- Publication d’un portail Starlight bilingue sur
search-console.bruniaux.com/docs/et/fr/docs/, avec recherche, navigation latérale, sommaire par page et liens de langue associés. - Les sources anglaises restent canoniques et sont publiées depuis une liste fermée. Les plans internes, exports machine-readable et rapports de validation bruts restent exclus du site.
- La landing dirige désormais l’installation, la configuration Google et Bing, les exemples, l’architecture, les limites de preuve, le changelog et la licence vers les pages publiques du site.
- Deux illustrations conceptuelles générées avec Gemini complètent des schémas HTML déterministes pour les flux de preuves et les actions protégées.
- Les titres de la landing et des scénarios utilisent des formulations factuelles et des chiffres explicites. La FAQ pose des questions directes en anglais et en français.
- Les pages documentaires publient désormais un
hreflang="x-default"vers leur version anglaise, et le site expose unllms.txtpublic limité aux ressources destinées aux utilisateurs.
[1.2.0] - 2026-10-06
Section titled “[1.2.0] - 2026-10-06”- Surface portée à 81 tools. Elle ajoute 19 tools Bing Webmaster, dont 15 lectures et 4 écritures protégées, ainsi que
compare_search_enginespour rapprocher les métriques Google et Bing sans fusionner leurs positions. - Les analyses
quick_wins,seo_striking_distanceetprune_candidatesacceptent les données Bing lorsque les métriques requises sont présentes. Les analyses qui exigent des périodes exactes ou une dimension page-requête en masse refusent explicitement Bing.
Validation
Section titled “Validation”- 851 tests passent et 851 tests sont collectés sur ce checkout.
- Les lectures Bing gardent leurs fenêtres observées, dérivent le CTR à partir des clics et impressions, et laissent les métriques propres au fournisseur dans
provider_metrics. - Les quatre écritures Bing sont couvertes par des tests avec réponses simulées. Aucun appel de mutation Bing ou IndexNow n’a été exécuté pendant ce gate local, leur comportement runtime reste
UNVERIFIED_RUNTIME.
Documentation and metadata
Section titled “Documentation and metadata”- Le README présente désormais
gsc-mcp-tools==1.2.0comme la première version publiée avec la surface Bing et relie le contrat API Bing. - Les références machine-readable, la description du paquet et les mots-clés de découverte sont alignés sur 81 tools, 851 tests et les fournisseurs Google/Bing.
- La description et les topics GitHub mentionnent Bing Webmaster Tools, IndexNow et le SEO technique. Les labels de workflow des issues restent inchangés.
- La configuration recommandée installe désormais l’outil une fois, utilise l’exécutable direct et limite Codex aux projets concernés afin d’éviter un processus
uvxsupplémentaire et un serveur global par tâche active. - La publication PyPI vérifie le tag, exécute les tests, contrôle le wheel construit puis utilise Trusted Publishing avec un jeton OIDC temporaire.
- Les écritures OAuth utilisent un remplacement atomique pour éviter de laisser un fichier de jeton partiellement écrit.
- Les soumissions IndexNow et Bing valident plus strictement le protocole, l’origine, les délimiteurs vides et les réponses du fournisseur.
- Les comparaisons multi-moteurs conservent les structures sérialisables, les positions absentes et omettent les deltas non comparables.
- Le point d’entrée module de la CLI et l’affichage des signes
%dans l’aide fonctionnent à nouveau.
Limits
Section titled “Limits”- Les fenêtres Bing ne sont pas supposées exactes et égales aux fenêtres Google.
compare_search_enginesomet les deltas lorsque cette condition n’est pas prouvée. GetKeywordStatsetGetRelatedKeywordsne sont pas exposés après des réponses HTTP 400 lors du canari réel expurgé.- Les sémantiques total-versus-restant des quotas Bing, les crawl issues non vides, les lignes imbriquées de backlinks et
RemoveFeedrestent non vérifiées en production. - Une réponse de soumission acceptée ne prouve ni crawl ni indexation.
[1.1.2] - 2026-08-26
Section titled “[1.1.2] - 2026-08-26”Dogfooding des 4 tools ajoutés en 1.1.0 sur une propriété réelle (cc.bruniaux.com, 400+ pages) : link_equity_map a échoué sur 3 pages des 25 ciblées.
internal_links_audit et link_equity_map traitaient un redirect 301/302 comme un échec de crawl
safe_fetch_html(url_safety.py) fixe volontairementfollow_redirects=False: suivre un redirect avec le clienthttpxstandard connecterait au serveur cible sans revalider son IP, ce qui contourne le DNS-pinning anti-SSRF.resp.raise_for_status()traite donc tout 3xx comme une erreur (httpx.HTTPStatusError), remontée comme échec de fetch.- Sur
cc.bruniaux.com, Search Console référence encore 3 pages enhttp://alors que le site sert duhttps://(redirect 301 permanent, scheme uniquement, même host et même path).link_equity_mapclassait les 3 danspages_failedau lieu de les crawler. - Fix :
_fetch_following_redirects()danslinks.pyboucle sursafe_fetch_html(bornée à 5 sauts), en relisant l’en-têteLocationsur l’exceptionhttpx.HTTPStatusErrorinterceptée. Chaque saut repasse parsafe_fetch_html, donc chaque nouvelle cible est re-vérifiée par le DNS-pinning : un redirect vers une IP privée ou un endpoint de metadata cloud reste refusé au même titre qu’une requête directe.internal_links_auditetlink_equity_maputilisent maintenant cette fonction ; les autres tools (page_technical_audit,schema_validate,sitemap_audit, …) gardentfollow_redirects=Falsesans changement, un redirect y est un résultat d’audit à signaler, pas une erreur de fetch à masquer. - 4 tests ajoutés dans
test_links.py: redirect http→https suivi avec succès, boucle bornée à 5 sauts (au-delà,fetch_erroravec message explicite), redirect vers un hôte bloqué toujours refusé (SSRF préservé), pagelink_equity_maprécupérée via redirect sortant depages_failed. 611 tests passent (607 + 4), aucune régression sur les 32 tests existants detest_links.py.
[1.1.1] - 2026-08-26
Section titled “[1.1.1] - 2026-08-26”Incident de production : tout uvx gsc-mcp-tools lancé sans lock depuis le 2026-07-28 (sortie de mcp 2.0.0) crashait à l’import, avant même l’ouverture du stdio MCP.
Dépendance mcp sans borne haute, résolue vers une v2 incompatible
pyproject.toml:mcp[cli]>=1.0.0n’avait pas de borne haute.uvrésolvait la dernière version publiée,mcp2.1.1, qui a supprimémcp.server.fastmcp.FastMCP(renomméMCPServer, changement d’API mentionné dans le message d’erreur du SDK).src/gsc_mcp/server.py:6importe encorefrom mcp.server.fastmcp import FastMCP, donc le process mourait à l’import avecModuleNotFoundError, avant toute négociation MCP. Côté client (Claude Code), ça remonte comme un simpleCONNECTION_CLOSED, sans indiquer la cause.- Fix :
mcp[cli]>=1.0.0,<2danspyproject.toml. Migration versMCPServernon faite dans ce correctif, seul le pin de sécurité est appliqué. - Détecté en testant
uvx gsc-mcp-toolsen direct depuis une session externe, hors de tout environnement de dev avec unmcp1.x déjà en cache.
[1.1.0] - 2026-08-24
Section titled “[1.1.0] - 2026-08-24”61 tools (+4), 607 tests (+61). Maillage interne et structure de titres, déclenchés par un appel avec un consultant SEO externe (2026-08-06) : deux leviers de ranking sur lesquels aucun tool existant ne mesurait rien.
Links : nouveau module, 2 tools sans auth Google
internal_links_audit(url):src/gsc_mcp/tools/links.py, nouveau module._LinkParser(stack-based, gère les conteneurs imbriqués comme un<nav>dans un<header>) tague chaque<a href>avec la zone sémantique où il se trouve (body,nav,footer,header,aside;role="navigation"/contentinfo/banner/complementarytraités comme leurs balises équivalentes). Sortie principale :footer_only_targets, les cibles internes liées depuis la nav/footer/aside et depuis le corps d’aucune page. Détecte aussi les ancres génériques et vides (FR+EN), les liens internes enrel=nofollow, les self-links. Verdicts :healthy|issues_found|fetch_error.link_equity_map(site, days=90, max_pages=25, delay_seconds=0.2): prend les pages les plus vues dans GSC, les crawle avec le même parser zoné, construit le graphe de liens internes, et croise avec les positions.underlinked_striking_distance(position 11-20 sans lien entrant en corps de page) est le mouvement de ranking le moins cher disponible. Reporte aussiorphan_candidates,footer_only_targets(site-wide) ethub_pages.max_pagesplafonné à 100 ;pages_crawled/pages_failed/coverage_notetoujours retournés, aucune couverture silencieusement tronquée.
Content : heading_audit, un 4e tool sans auth
heading_audit(url):_HeadingParserdédié danscontent.py(le parser dedrift.pyne couvre que h1-h3 et jette l’ordre du document ; son format de sortie est persisté dans les baselines drift, donc laissé intact). Vérifie l’unicité du H1, les sauts de niveau (H2 direct à H4), la duplication mot pour mot du title et du H1 (gâche un second angle sur le mot clé visé), les titres qui ne portent aucune information (liste noire FR+EN), et la densité de mots par H2. Verdicts :healthy|issues_found|fetch_error.
SEO : prune_candidates, garde-fou anti-suppression
prune_candidates(site, days=180): classe les pages enhas_traffic,impressions_no_clicks,low_impressions,zero_impressionssur 180 jours de données GSC, et ne retourne volontairement aucune liste « à supprimer ». Une page avec des clics ne peut jamais apparaître comme candidate, par construction. Cas de non-régression :tests/test_seo.py::test_prune_solar_panel_local_pages_are_protected, 300 pages locales courtes indexées et amenant du trafic, le genre de pages qu’un outil basé sur la longueur du contenu recommande de supprimer.CLAUDE.md: nouvelle règle « No destructive recommendation without data », aucun appelant (agent ou skill) ne peut proposer une suppression, unnoindexou une consolidation sans avoir lu clics/impressions/statut d’indexation sur au moins 90 jours.
Détection de questions en français
cross.py:content_briefne classait les requêtes-questions que surwho/what/when/where/why/how, un site francophone remontait donc systématiquement zéro question détectée. Ajout de 12 mots FR, des formes multi-mots (est-ce que), et des variantes sans accent.
4 skills projet + corpus de routage
.claude/skills/internal-linking-audit/,heading-audit/,link-equity-map/,onpage-audit/: le dernier enchaîne les 4 nouveaux tools pluspage_technical_audit,content_quality,schema_validate,content_briefetcrux_page_vitalssur une URL, avec la règle anti-suppression appliquée à chaque recommandation..claude/routing-corpus/: scénarios positifs/négatifs pour le routage BM25 des 4 nouveaux skills, au format lu par le corpus de routage global (~/.claude/hooks/routing/). Nécessite une version du routeur qui scanne aussi$CLAUDE_PROJECT_DIR/.claude/routing-corpus/en plus du corpus global, changement hors dépôt.scripts/routing-eval.js: recalcule les scores BM25 et les seuils de calibration sans toucher au cache du routeur global, pour mesurer les collisions entre cibles avant de committer un nouveau corpus.
Deux skills invisibles depuis leur création
.claude/skills/content-opportunities/SKILL.md: un:dans la description cassait le parsing YAML du frontmatter ; le skill retombait sur son titre H1 comme description et ne s’est jamais auto-déclenché correctement..claude/agents/gsc-content-optimizer.md: même cause, mais l’agent n’apparaissait pas du tout dans la liste des agents disponibles.
Changed
Section titled “Changed”Déclenchement en français
-
9 skills (
ai-overviews-impact,cannibalization-check,content-opportunities,indexing-audit,page-deep-dive,schema-audit,seo-weekly-report,sitemap-audit,traffic-drop-diagnosis) et 9 agentsgsc-*: descriptions enrichies avec des verbatims français réels pour le matching du modèle. Distinct du routage BM25, qui indexe des scénarios séparés, pas les descriptions. -
README.md,pyproject.toml,src/gsc_mcp/cli.py,docs/machine-readable/llms.txt: comptes 57 → 61 tools, 546 → 607 tests, partout où ils apparaissaient (badges, docstrings, tableau des tools, module map, section decision tree).
Tool count
Section titled “Tool count”57 → 61 tools. Test count : 546 → 607.
[1.0.1] - 2026-07-27
Section titled “[1.0.1] - 2026-07-27”Durcissement suite à un scouting concurrentiel (6 MCP Google Ads/GA4/GSC analysés). Pas de nouveau tool.
Auth : fallback sur refresh token révoqué
auth.py:_get_oauth_credscatch désormaisgoogle.auth.exceptions.RefreshErrorautour decreds.refresh(Request()). Un refresh token révoqué ou expiré côté Google supprime le token en cache et retombe sur le flux de ré-auth normal, au lieu de lever la même exception à chaque appel jusqu’à intervention manuelle.tests/test_auth.py:test_oauth_creds_refresh_error_falls_back_to_reauthcouvre le nouveau chemin (token supprimé, fallback versRuntimeError("No credentials...")quandGSC_CREDENTIALS_PATHest absent).
Documentation : protocole de confirmation pour les tools d’écriture
CLAUDE.md: nouvelle section décrivant les 4 étapes attendues des agents/skills appelants avant d’invoquer un tool qui mute un état externe (submit_url,submit_batch,sitemaps_delete,indexnow_submit,submit_sitemap) : état actuel, rayon d’impact, confirmation explicite, vérification post-appel. Convention d’appel documentée, pas de changement de comportement dans les tools eux-mêmes.docs/machine-readable/llms.txt: sectionWRITE-TOOL PROTOCOLajoutée en écho, plus une ligne sur le fallbackRefreshErrordans le blocAUTH.
Changed
Section titled “Changed”pyproject.toml: version 0.6.2 → 1.0.1 (rattrapage du drift : les waves 0.7.0 à 1.0.0 avaient mis à jourCHANGELOG.mdsans bumperpyproject.toml). Description mise à jour pour refléter les 57 tools actuels au lieu des 43 d’origine.README.md: badge tests 545 → 546, ligne “Latest” mise à jour.
Écarté (évalué, non fait)
Section titled “Écarté (évalué, non fait)”- Catégorisation centralisée des erreurs HTTP (403/404 → message actionnable) dans
retry.py, sur le modèle du fork Googlefourdots/Google-Marketing-MCPs.ai_overviews_impact(analytics.py:314) catch déjàHttpErrorau cas par cas pour transformer un 403 en réponse structurée ; généraliser dansretry.pyaurait cassé ce pattern existant pour un gain flou.
Tool count
Section titled “Tool count”57 tools (inchangé). Test count : 545 → 546.
[1.0.0] - 2026-06-26
Section titled “[1.0.0] - 2026-06-26”57 tools (+3), 545 tests (+27). Wave C : trois nouveaux outils Technical + enrichissement schema_validate.
Technical : ai_visibility_audit
ai_visibility_audit(url): lit{origin}/robots.txtviasafe_fetch_html+ parseurllib.robotparser. Vérifie 9 crawlers IA connus : GPTBot, Anthropic-ai, Claude-User, PerplexityBot, CCBot, Google-Extended, cohere-ai, Bytespider, OAI-SearchBot. Vérifie aussi la présence de{origin}/llms.txt(fichier MCP/discoverability). Verdicts :open(tous autorisés ou pas de robots.txt) |partial(certains bloqués) |closed(tous bloqués) |fetch_error.
Technical : gbp_deprecation_lint
gbp_deprecation_lint(url): fetch viasafe_fetch_html+ scan regex sur 5 patterns de features GBP dépréciées : liens.business.site(GBP Websites sunset mars 2024), Reserve with Google (déprécié juin 2025), widget appointments GBP, Google Maps Reserve flow, GBP chat widget. Verdicts :clean|deprecated_found|fetch_error. Pas d’auth requise.
Technical : pagespeed_audit
pagespeed_audit(url, strategy="mobile"): appelle l’API PageSpeed Insights v5 viahttpx. Retourne score Lighthouse performance, 6 métriques CWV (FCP, LCP, TBT, CLS, Speed Index, TTI), et 3 opportunités d’amélioration prioritaires. RequiertGOOGLE_API_KEYenv var ; retourneverdict="missing_key"si absente. Verdicts :good(≥90) |needs_improvement(50-89) |poor(<50) |missing_key|fetch_error.
Changed
Section titled “Changed”schema_validate : détection des types dépréciés pour les rich results
-
Ajout de
_DEPRECATED_RICH_RESULTS: FAQPage (mai 2026), HowTo (sept 2023), ClaimReview, EstimatedSalary, VehicleListing, SpecialAnnouncement (juin 2025). Chaque schema détecté inclut désormais un champdeprecated_rich_result(string ou null). -
registry.py: +3 tools. Docstring : 54 → 57. -
properties.py:_ALL_TOOLS+= 3 entrées. Docstringget_capabilities: 54 → 57. -
tests/test_registry.pyettests/test_properties.py: compteurs 54 → 57. -
README.md: badge tests 518 → 545, comptes 54 → 57.
Tool count
Section titled “Tool count”54 → 57 tools. Test count : 518 → 545.
[0.9.0] - 2026-06-26
Section titled “[0.9.0] - 2026-06-26”54 tools (+4), 518 tests (+51). Wave B de la seconde intégration claude-seo (MIT uniquement).
Content : preload_audit
preload_audit(url): fetch viasafe_httpx_get(retourne lehttpx.Responsecomplet, donc headers inclus, SSRF-safe). Détecte : blocs<script type="speculationrules">parsés en JSON pour les actionsprefetch/prerender, header HTTPSpeculation-Rules(Chrome 122+),<link rel="preload">avec extraction des attributsas/href/fetchpriority,<link rel="prerender">déprécié (sunset Chrome 120), bloqueur bfcachecache-control: no-store. Génère une liste d’issues avec sévérité (high/medium/low) etcheck. Verdicts :optimised(prefetch+prerender + pas de bfcache killer) |improvements_available(règles présentes mais issues) |not_implemented(aucune Speculation Rules) |fetch_error.
CrUX : crux_lcp_subparts
crux_lcp_subparts(url, form_factor="PHONE"): même endpoint CrUX quecrux_page_vitals, requête 5 métriques simultanément (LCP global + 4 subparts). Retournelcp_p75_ms,lcp_rating, etsubparts:ttfb_ms,resource_load_delay_ms,resource_load_duration_ms,render_delay_ms,dominant_phase(nom court du subpart avec la valeur p75 la plus élevée). Clé API absente =verdict="missing_key"(pas de RuntimeError, contrairement àcrux_page_vitals). Verdicts :good|needs_improvement|poor|not_enough_data|missing_key|fetch_error.
Indexing : indexnow_submit
indexnow_submit(site, key, urls): POST vershttps://api.indexnow.org/indexnow. Protocole open source, consommé par Bing, Yandex, Seznam, Naver (pas Google).validate_url_strictsur chaque URL avant envoi (SSRF-safe), URLs invalides comptées dansskipped_invalid. Host extrait du paramètresiteviaurlparse.keyLocationdérivé automatiquement :{site}/{key}.txt. Sans@with_retry(pas une API Google). HTTP 200/202 =oksi aucun skip,partialsi skips ; autres codes =error. Verdicts :ok|partial|error.
SEO : parasite_risk
parasite_risk(site, urls): analyse pure de chemins URL, sans fetch HTTP. Détecte les patterns de la politique Google du 2024-11-19 sur le site-reputation abuse. Trois niveaux de risque par URL : high (/sponsored/,/affiliate/,/partner/,/brand-studio/,/paid-content/,/native-advertising/, sections commerce produitbest-deals/top-picks), medium (/advisor/,/underscored/,/select/,/commerce/d’après Forbes Advisor / CNN Underscored / WSJ), low (query params?ref=,?aff=,?partner=).site_risk= risque maximum sur toutes les URLs. Verdicts :clean|at_risk|high_risk. Pas d’auth requise. Adapté declaude-seo(agricidaniel, MIT).
Changed
Section titled “Changed”registry.py: import + enregistrement des 4 nouveaux tools. Docstring : 50 → 54 tools.properties.py:_ALL_TOOLS+= 4 entrées. Docstringget_capabilities: 50 → 54.tests/test_registry.pyettests/test_properties.py: compteurs 50 → 54.README.md: badge tests 467 → 518, comptes tools 50 → 54 (intro, section Tools, CLI, feature set), 4 nouvelles lignes dans le tableau.docs/machine-readable/llms.txt: module map complété pour les 4 nouveaux tools, comptes 50 → 54 et 467 → 518, patch points ajoutés.
Tool count
Section titled “Tool count”50 → 54 tools. Test count : 467 → 518.
[0.8.0] - 2026-06-26
Section titled “[0.8.0] - 2026-06-26”50 tools (+3), 467 tests (+38). Wave A de la seconde intégration claude-seo (MIT uniquement).
Content : 3 nouveaux tools sans auth Google
content_quality(url): fetch viasafe_fetch_html+ extraction du texte visible (stdlibhtml.parser, skip des blocs script/style/nav/footer). Score quatre axes : filler phrases (liste MIT declaude-seo,_AI_PATTERNSCC BY-SA 4.0 volontairement exclu), densité informationnelle (entités nommées + nombres pour 100 tokens), répétition de bigrammes, contenu thin (<300 tokens). Score global pondéré (filler 35%, densité 35%, répétition 20%, longueur 10%). Flags :filler,low-density,repetitive,thin-content. Verdicts :good|needs_work|thin_content|fetch_error.hreflang_audit(url): fetch +_MetaParserstdlib. Vérifie le self-referencing tag, la présence d’x-default, les codes ISO 639-1 (détectejp→ja,eng→enà trois lettres), les régions ISO 3166-1 Alpha-2 (détecteUK→GB), la cohérence de protocole HTTP/HTTPS sur le set d’alternates. Audit de la page cible uniquement, les return tags bidirectionnels nécessitent un fetch séparé. Verdicts :valid|issues_found|no_hreflang|fetch_error.page_technical_audit(url):validate_url_strict+httpx.Client(follow_redirects=False). Audite : longueur du title (30-60), longueur de la meta description (50-160), directive meta robots (noindex= criticité haute), présence et cohérence du canonical, viewport, attributlangsur<html>, trois security headers (X-Frame-Options,X-Content-Type-Options,Referrer-Policy), détection de redirect (3xx + cible), accès Googlebot en robots.txt (fetch viasafe_fetch_html+ parse viaurllib.robotparser.parse()stdlib pour rester dans la couche SSRF). Verdicts :healthy|issues_found|fetch_error.src/gsc_mcp/tools/content.py: module dédié avec deux parsers stdlib (_TextExtractoret_MetaParser, héritant dehtml.parser.HTMLParser) et la constante_FILLER_PHRASES(35 patterns, adaptés declaude-seo, MIT,agricidaniel).
Changed
Section titled “Changed”registry.py: import + enregistrement des 3 nouveaux tools (content_quality,hreflang_audit,page_technical_audit). Docstring : 47 → 50 tools.properties.py:_ALL_TOOLS+= 3 entrées. Docstringget_capabilities: 47 → 50.README.md: badge tests 429 → 467, comptes tools 47 → 50 (intro, section Tools, CLI, paragraphe feature set), nouvelle famille “Content” dans le tableau.docs/machine-readable/llms.txt: module map complété pourcontent.py, comptes 47 → 50 et 429 → 467, patch points de test ajoutés.
Tool count
Section titled “Tool count”47 → 50 tools. Test count : 429 → 467.
[0.7.0] - 2026-06-26
Section titled “[0.7.0] - 2026-06-26”47 tools (+4), 429 tests (+147). Intégration sélective d’assets MIT de AgriciDaniel/claude-seo.
Sécurité : module SSRF unifié
src/gsc_mcp/url_safety.py: protection SSRF et DNS-rebinding centralisée. Bloque les IPs privées/loopback/réservées, l’IPv4 obfusqué (décimal, hexadécimal, octal, FQDN avec trailing dot), les endpoints de métadonnées cloud (AWS IMDS169.254.169.254, Azure169.254.169.254/metadata, GCPmetadata.google.internal, Oracle, Alibaba). DNS-pinning via patch desocket.getaddrinfosous lock pour sécuriser les redirections. API publique :validate_url(),validate_url_strict(),safe_httpx_get(),safe_fetch_html(). Remplace les guards ad-hoc présents danstechnical.pyetsitemaps.py. Adapté declaude-seo(agricidaniel, MIT).
Technical : génération JSON-LD
schema_generate(schema_type, ...): génère un bloc JSON-LD Schema.org pour quatre types à fort impact SEO.reservation(FoodEstablishmentReservation avec provider, start_time, party_size, customer),order_action(OrderAction avec merchant, order_url, delivery methods),discussion(DiscussionForumPosting avec headline, author, url, date_published, comment_count optionnel),profile(ProfilePage avec mainEntity Person, sameAs, knowsAbout, worksFor). Aucun appel réseau, aucune auth requise. Adapté declaude-seo(agricidaniel, MIT).
Drift monitoring : 3 nouveaux tools
drift_baseline(url, with_cwv): capture un snapshot SEO d’une URL (title, meta description, robots, canonical, H1-H3, JSON-LD, OpenGraph, status HTTP, SHA-256 du HTML et des schemas). Stocké en SQLite viaplatformdirs.user_data_dir("gsc-mcp")/drift/baselines.db(WAL mode). CWV optionnel siCRUX_API_KEYest configuré.drift_compare(url, with_cwv): fetch live puis applique 17 règles de diff (méthodologie Dan Colta, MIT) : 8 CRITICAL (changement de title, H1, canonical, meta robots, suppression de schema, perte de status 200, régression LCP/CLS), 6 WARNING (ajout/suppression H2-H3, modification meta description, ajout noindex, dégradation INP), 3 INFO (modification OpenGraph, ajout schema, variation de longueur HTML > 20%). Verdict global :no_drift|drift_detected.drift_history(url, limit): liste les comparaisons stockées pour une URL avec le résumé des findings par run.
Documentation SEO sourcée
docs/seo-knowledge/comparison-rules.md: 17 règles de drift avec seuils et sévérités. Source : Dan Colta (MIT).docs/seo-knowledge/cwv-thresholds.md: seuils LCP/INP/CLS/FCP/TTFB avec méthodologie de scoring (INP remplace FID).docs/seo-knowledge/local-seo-signals.md: signaux 2026 Whitespark/BrightLocal/Sterling Sky pour le SEO local et l’AI search.docs/seo-knowledge/serp-overlap-methodology.md: algorithme de clustering par overlap top-10 (Lutfiya Miller).
Attribution
NOTICE: copyright MIT declaude-seo(Copyright (c) 2026 agricidaniel), contribution de Dan Colta, liste des assets portés, exclusions explicites (CC BY-SA word lists).README.md: section Credits pointant versgithub.com/AgriciDaniel/claude-seo.
Changed
Section titled “Changed”technical.py:_reject_ssrfinterne remplacé parurl_safety.validate_url_strict. Trois annotationsOptional[list]corrigées enOptional[list[str]]pour la compatibilité CLI.sitemaps.py: origin-check des URLs enfant rebranché sururl_safety.validate_url_strict.cli.py:_type_kind()étend la détection aux annotationstyping.Optional[X](=typing.Union[X, None]) etbool._build_subparser()gère le casboolavecstore_true/store_false. Corrigeait unsys.exit(1)silencieux qui cassait la construction des 47 parsers.registry.py+properties.py: 4 nouveaux tools enregistrés (schema_generate,drift_baseline,drift_compare,drift_history).
- CLI : 56 tests en échec causés par
_type_kind()qui ne gérait pastyping.Optionalnibool(annotations des tools drift et schema_generate). - Tests
test_technical.py+test_sitemap_audit.py: cible de mock DNS corrigée (gsc_mcp.url_safety.socket.getaddrinfoau lieu degsc_mcp.tools.technical.socket.getaddrinfo). test_technical.py: assertion SSRF corrigée ("192.168.1.1" in result["error"]au lieu de"Blocked").test_properties.py+test_registry.py: compteurs hardcodés 43 → 47.
Tool count
Section titled “Tool count”43 → 47 tools. Test count : 282 → 429.
[0.6.2] - 2026-06-25
Section titled “[0.6.2] - 2026-06-25”- README: “What’s New” section summarizing v0.6.0 and v0.6.1 highlights for users landing on the repo
- README: Troubleshooting section covering the 6 most common setup issues (auth, GA4, CrUX, Indexing API quota, uvx launch)
[0.6.1] - 2026-06-24
Section titled “[0.6.1] - 2026-06-24”Refactor interne et corrections sans nouveau tool.
- Entrypoint
gsc-mcp-toolsmanquant danspyproject.toml:uvx gsc-mcp-toolséchouait avec “An executable named gsc-mcp-tools is not provided by package gsc-mcp-tools”. Les deux noms (gsc-mcpetgsc-mcp-tools) pointent maintenant sur le mêmegsc_mcp.server:main - README : badge tests corrigé (286 → 282), compteur dev section corrigé (222+ → 282), tableau détaillé complété avec les 4 tools manquants (
sitemap_audit,crux_page_vitals,crux_history,schema_validate), descriptions Cross traduites de FR en EN
Changed
Section titled “Changed”- Ponytail refactor (complexité sans valeur supprimée) :
auth.py: helper_ga4_creds()extrait pour éliminer la duplication entreget_ga4_serviceetget_alpha_ga4_serviceanalytics.py:_SEARCH_TYPESdéplacé en variable locale danssearch_type_breakdown(pas d’autres usages)seo.py: helper_two_periods(days)ajouté pour déduplication du calcul de dates danstraffic_dropsetseo_lost_queriessitemaps.py:DefusedXmlExceptionajouté auexceptinterne de_fetch_xml, bloctry/exceptexterne suppriméindexing.py:_submit_batch_implfusionné danssubmit_batch, couche de délégation suppriméecross.py: dicts parallèles remplacés par de l’arithmétique directe dans la renormalisation depage_health_score
- Tests :
test_seo_v2.pyettest_sitemaps_v2.pyfusionnés danstest_seo.pyettest_sitemaps.pyrespectivement, puis supprimés.test_scaffold.pysupprimé (couvert par conftest et imports) - Test count : 268 → 282
[0.6.0] - 2026-06-24
Section titled “[0.6.0] - 2026-06-24”7 nouveaux tools, 43 tools au total, 268 tests.
Analytics GSC : nouvelles variations search type
discover_performance(site, days, limit): performances Google Discover par page. Utilise"type": "discover"dans le corps de requête. La dimensionqueryn’est pas supportée par Discover, seulpageest retourné. Trié par impressions décroissantes.news_performance(site, days, limit): identique àdiscover_performancemais pour Google News ("type": "googleNews").search_type_breakdown(site, url, days): 5 appels séquentiels à_fetch_rows(web, discover, googleNews, image, video), agrège clicks et impressions par type. Paramètreurloptionnel pour filtrer sur une page spécifique.ai_overviews_impact(site, days, limit): requête avec"dimensions": ["query", "searchAppearance"]et"dataState": "all". Retourne un dict{"error": "AI_OVERVIEWS_NOT_AVAILABLE"}sur HttpError 400/403 (propriétés sans données AI Overviews) sans lever d’exception. Les erreurs 500+ restent remontées.
Cross GSC+GA4 : outils composites
page_health_score(site, url, property_id, hostname, country): score composite 0-100 combinant 4 sources. GSC (30 pts viainspect_url), GA4 (25 pts viaga4_page_performance), CrUX (25 pts viacrux_page_vitals, LCP+INP+CLS), et schema (20 pts viaschema_validate). Chaque composant est isolé dans untry/except RuntimeError: si une source est absente (credentials manquants), ses points sont 0 et le score est renormalisé sur les sources disponibles.content_brief(site, page_url, days, property_id): intelligence éditoriale par page. Filtre les requêtes GSC surpage_url(normalisation via_normalize_url), trie par clicks, extrait les requêtes “question” (who/what/when/where/why/how) depuis la liste complète filtrée. Enrichit avecga4_page_performance(sessions, engagement_rate) en dégradation gracieuse si GA4 est absent.
GA4 : funnel via v1alpha
ga4_funnel(steps, start_date, end_date, property_id): rapport de funnel multi-étapes viaAlphaAnalyticsDataClient.run_funnel_report(). Validation stricte : moins de 2 étapes retourne{"error": "INVALID_STEPS"}. Chaque étape est un dict{"name": str, "event": str}. Taux de conversion par étape relatif à l’étape 1 (qui est toujoursnull). Nouveau getterget_alpha_ga4_service()dansauth.py, même scope et token que la beta.
Changed
Section titled “Changed”- Tool count : 36 -> 43
- Test count : 222 -> 268
get_capabilitiesdocstring : “36” -> “43”,_ALL_TOOLSétendu avec les 7 nouveaux tools
[0.5.0] - 2026-06-23
Section titled “[0.5.0] - 2026-06-23”4 nouveaux tools, filtres hostname/country sur tous les tools GA4, et 55 nouveaux tests. 36 tools au total, 222 tests.
GA4 : filtres hostname et country (tools existants)
_build_dimension_filter(hostname, country, base_filter)dansga4.py: construit unFilterExpressionseul ou un AND group (viaFilterExpressionList) selon le nombre de filtres actifs. Backward-compatible : avecNone, None, le comportement est identique à avant- Paramètres
hostname: str | None = Noneetcountry: str | None = Noneajoutés àga4_organic_landing_pages,ga4_traffic_sources,ga4_page_performance,ga4_user_behavior,ga4_conversion_funnel - Paramètre
hostname: str | None = Noneajouté àga4_realtime(countrynon disponible surrunRealtimeReport) - Mêmes paramètres propagés à
traffic_health_checketpage_analysis(passés àga4_organic_landing_pagesen interne) - 20+ nouveaux tests dans
test_ga4.pyet correction detest_pa_metadanstest_cross.py
CrUX : Core Web Vitals réels (nouveaux tools)
crux_page_vitals(url, form_factor): interroge le Chrome UX Report API (:queryRecord). Retourne LCP, INP, CLS, FCP, TTFB avec ratings good/needs_improvement/poor, percentiles p75, et un verdict globalgood/needs_improvement/poor/not_enough_data. NécessiteCRUX_API_KEYcrux_history(url, form_factor): séries temporelles hebdomadaires via:queryHistoryRecord. Retourne jusqu’à 25 semaines de p75 par métrique pour tracker les régressions CWVCRUX_API_KEY: variable d’env distincte de l’auth GSC (Google API Key simple, pas service account). La Chrome UX Report API doit être activée dans le projet GCP- Nouvelle dépendance :
httpx>=0.27.0 - 16 nouveaux tests dans
tests/test_crux.py
Sitemaps : audit de couverture (nouveau tool)
sitemap_audit(site, sitemap_url): fetch un sitemap (ou sitemap index) via httpx, parse les URLs avecdefusedxml.ElementTree(prévient XXE et billion-laughs), cross-référence contre 90 jours de GSC viaget_search_analytics. Gère les sitemap index avec une récursion d’un niveau. Verdicts :empty|fetch_error|partial(>20% URLs absentes de GSC) |healthy- Protection SSRF : les child sitemaps d’un sitemap index sont validés contre l’origin du sitemap parent (
follow_redirects=False) - Nouvelle dépendance :
defusedxml>=0.7.1 - 7 nouveaux tests dans
tests/test_sitemap_audit.py
Technical : validation JSON-LD (nouveau tool)
schema_validate(url): fetch n’importe quelle URL publique via httpx, extrait tous les blocs<script type="application/ld+json">viahtml.parser(stdlib, pas de dépendance externe), valide les champs requis par type (Article, LocalBusiness, FAQPage, Product, WebSite, BreadcrumbList, SoftwareApplication…), et suggère des schemas manquants selon les patterns d’URL (/faq → FAQPage, /blog/ → BlogPosting, etc.). Verdicts :healthy|missing_schemas|invalid_schemas|fetch_error. Ne nécessite pas d’auth- 15 nouveaux tests dans
tests/test_technical.py
Changed
Section titled “Changed”- Tool count : 32 → 36
- Test count : 167 → 222
get_capabilitiesdocstring : “32” → “36”,_ALL_TOOLSétendu avec les 4 nouveaux toolspyproject.tomldescription mise à jour pour refléter les 36 tools et les nouvelles catégories
[0.4.2] - 2026-06-22
Section titled “[0.4.2] - 2026-06-22”- Tous les tools GA4 (
ga4_organic_landing_pages,ga4_traffic_sources,ga4_page_performance,ga4_realtime,ga4_user_behavior,ga4_conversion_funnel) et les tools cross (traffic_health_check,page_analysis) acceptent un paramètre optionnelproperty_id: str = None. Quand fourni, il overrideGA4_PROPERTY_IDsans modifier la config. Permet de requêter plusieurs properties GA4 depuis une seule instance MCP. get_ga4_property_id(override=None)dansauth.py: accepte un override direct, court-circuite la lecture de l’env var. Sans override, comportement identique à avant.- 4 nouveaux tests :
test_get_ga4_property_id_override_takes_precedence,test_get_ga4_property_id_override_no_env_needed,test_thc_property_id_propagated,test_pa_property_id_propagated
[0.4.1] - 2026-06-22
Section titled “[0.4.1] - 2026-06-22”Corrections de cohérence et consolidation interne : pas de nouveaux tools.
get_capabilitiesretournait 18 tools sur les 32 réellement disponibles. Les 14 manquants (analytics_anomalies,seo_striking_distance,seo_cannibalization,seo_lost_queries,sitemaps_delete,sitemaps_get, 6 tools GA4,traffic_health_check,page_analysis) sont maintenant listésinspect_url,batch_url_inspection,check_indexing_issuesappelaientwebmasters/v3, qui n’expose pasurlInspection. Ces trois tools levaient uneAttributeErrorau runtime sur chaque appel. Corrigé en passant sursearchconsole/v1(API qui expose la ressourceurlInspection)traffic_health_checketpage_analysisincluent maintenant un champnotedans leur réponse JSON pour avertir que GSC a un décalage de 3 jours vs GA4, les ratios sont donc approximatifsga4_organic_landing_pagesajoute un champnotequand le nombre de résultats atteint la limite, signalant une troncature potentielle
Changed
Section titled “Changed”- Tous les tools GSC (analytics, SEO, sitemaps, properties) consolidés sur
searchconsole/v1. Le clientwebmasters/v3(get_gsc_service) est supprimé deauth.py.searchconsole/v1expose les mêmes ressourcessites,searchanalytics,sitemapsen plus deurlInspection
- Couverture
quick_winsaméliorée : 4 nouveaux cas couvrant les pages à CTR zéro avec impressions suffisantes, l’exclusion sous le seuil d’impressions et l’exclusion des pages déjà au benchmark de CTR
[0.4.0] - 2026-06-22
Section titled “[0.4.0] - 2026-06-22”Phase 3: 2 tools cross-platform GSC+GA4, nouveau module cross.py.
traffic_health_check(site, days): compare les clics GSC agrégés avec les sessions organiques GA4 pour détecter les écarts de tracking. Retourne un statut parmino_gsc_data,tracking_gap(ratio < 0.6),filter_issue(ratio > 1.3) ouhealthy. GA4 interrogé aveclimit=10000pour éviter les sous-comptages sur les gros sitespage_analysis(site, days, limit): jointure page par page entre GSC (dimensions=[“page”]) et GA4 (landing pages organiques). Les pages présentes dans une seule source sont incluses avec les champs manquants àNone. Chaque page reçoit unopportunity_score = log10(impressions+1)*10 + engagement_rate*100 + log10(conversions+1)*20, trié décroissant, tronqué àlimit_normalize_url(url)helper interne: ramène URLs absolues (GSC) et paths GA4 au même chemin nu, sans scheme, host, query ni slash final, pour fiabiliser la jointureengagement_ratedérivé danscross.pycommeengaged_sessions/sessions(formule GA4 native), sans modifier la sortie dega4_organic_landing_pages- 24 nouveaux tests dans
tests/test_cross.py, dont les boundaries 0.6 et 1.3 du ratio, les cas GSC-only, GA4-only, trailing slash et query string
Changed
Section titled “Changed”- Tool count: 30 vers 32
[0.3.0] - 2026-06-22
Section titled “[0.3.0] - 2026-06-22”Phase 2: 6 new GA4 tools, new dependency, new environment variable.
ga4_organic_landing_pages(start_date, end_date, limit): sessions, engaged sessions, bounce rate, average session duration, conversions and revenue for organic landing pages. Uses thesessionMedium=organicfilter onlandingPagePlusQueryStringga4_traffic_sources(start_date, end_date): sessions and conversions broken down by channel group, source and mediumga4_page_performance(start_date, end_date, page_path): 7 metrics per page path (page views, active users, average session duration, engagement rate, bounce rate, conversions, revenue). Optionalpage_pathparameter adds a CONTAINS filterga4_realtime(): active users right now, by screen name, country and device. No date range, usesrun_realtime_reportdirectlyga4_user_behavior(start_date, end_date): singlebatch_run_reportscall returning three breakdowns (by device, by country top 20, by user type new/returning)ga4_conversion_funnel(start_date, end_date, event_name): two sequentialrun_reportcalls. First lists pages with conversions > 0; second lists events, optionally filtered by exact event name- New dependency:
google-analytics-data>=0.18.0(Google Analytics Data API v1beta, protobuf-based client) - New environment variable:
GA4_PROPERTY_ID(numeric property ID, e.g.123456789). Validated lazily on first GA4 tool call, never at startup. GSC-only users are not affected get_ga4_service()andget_ga4_property_id()inauth.py, reusing the same_resolve_credspath as GSC and Indexing clients
Changed
Section titled “Changed”- Tool count: 24 → 30
[0.2.0] - 2026-06-22
Section titled “[0.2.0] - 2026-06-22”Phase 1: 6 new tools, no new dependencies.
seo_striking_distance(site, days, min_impressions): queries in positions 8-15 sorted by impressions desc. Separate band fromquick_wins(4-15), intended for queries one push away from page 1seo_cannibalization(site, days, min_impressions): detects queries split across multiple pages using an HHI conflict score (1 - sum(share²)). Zero-click groups use uniform1/nfallback to avoid division by zero. Filters on per-query total impressions, not per-pageseo_lost_queries(site, days): flags queries with a click drop ≥ 80% vs the previous period, requiring at least 5 previous clicks. Iterates over the previous period to catch fully-vanished queries. Same two-window no-lag pattern astraffic_dropsanalytics_anomalies(site, days, threshold): Z-score anomaly detection on daily clicks viastatistics.pstdev. Returnsanomalies = []when std is zero (constant or all-zero series) to handle low-traffic sites safelysitemaps_delete(site, sitemap_url): deletes a sitemap with a safety check before any API call (URL must end with.xmlor contain/sitemap, raisesValueErrorotherwise)sitemaps_get(site, sitemap_url): fetches a single sitemap resource and normalises it to the same flat shape aslist_sitemaps(warnings and errors coerced to int)- 62 new tests (35 SEO, 15 sitemaps, 9 analytics anomalies): all mocked, no API calls
Changed
Section titled “Changed”- Tool count: 18 → 24
[0.1.0] - 2026-06-20
Section titled “[0.1.0] - 2026-06-20”Initial release.
- 18 MCP tools across 6 categories: meta, properties, analytics, SEO, inspection, indexing, sitemaps
submit_batchusing true HTTP multipart batch vianew_batch_http_request(), chunked at 100 URLs per request. Avoids the late-binding closure bug with a_make_callback(url)factory pattern- Dual OAuth scope architecture: separate clients for GSC (
auth/webmasters) and Indexing API (auth/indexing), because the Indexing API rejects webmasters tokens - OAuth flow with token stored as JSON (
google.oauth2.credentials.Credentials.to_json()) instead of pickle - Service Account support as an alternative to OAuth (set
GSC_SERVICE_ACCOUNT_PATH) - Exponential backoff retry on HTTP 429/500/502/503/504 via
with_retry()decorator, no retry on 404 - In-memory quota tracker (
QuotaTracker) with configurable limit and warn threshold (warns at 180/200 by default) with_meta()wrapper on all tool outputs: every response includes a_metablock with tool name and call parameters, so Claude has full context on what was fetchedquick_winstool scoring CTR opportunity vs benchmark by SERP positioncheck_indexing_issuescategorizing URLs intonot_indexed,robots_blocked,fetch_error,canonical_issue,indexedtraffic_dropsdiagnosing drops asranking_loss,ctr_collapse, ordemand_decline- Full test suite: 52 tests, fully mocked, no Google API calls required