gsc-mcp-tools 0.4.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. gsc_mcp_tools-0.4.2/.claude/settings.local.json +53 -0
  2. gsc_mcp_tools-0.4.2/.env.example +19 -0
  3. gsc_mcp_tools-0.4.2/.gitignore +13 -0
  4. gsc_mcp_tools-0.4.2/.idea/.gitignore +10 -0
  5. gsc_mcp_tools-0.4.2/.idea/google-search-console-mcp.iml +8 -0
  6. gsc_mcp_tools-0.4.2/.idea/modules.xml +8 -0
  7. gsc_mcp_tools-0.4.2/.idea/vcs.xml +6 -0
  8. gsc_mcp_tools-0.4.2/.idea/workspace.xml +97 -0
  9. gsc_mcp_tools-0.4.2/.python-version +1 -0
  10. gsc_mcp_tools-0.4.2/.superpowers/sdd/progress.md +17 -0
  11. gsc_mcp_tools-0.4.2/CHANGELOG.md +101 -0
  12. gsc_mcp_tools-0.4.2/CLAUDE.md +62 -0
  13. gsc_mcp_tools-0.4.2/LICENSE +21 -0
  14. gsc_mcp_tools-0.4.2/PKG-INFO +226 -0
  15. gsc_mcp_tools-0.4.2/README.md +195 -0
  16. gsc_mcp_tools-0.4.2/docs/architecture.md +150 -0
  17. gsc_mcp_tools-0.4.2/docs/google-setup.md +174 -0
  18. gsc_mcp_tools-0.4.2/docs/starter-prompt.md +120 -0
  19. gsc_mcp_tools-0.4.2/pyproject.toml +54 -0
  20. gsc_mcp_tools-0.4.2/src/gsc_mcp/__init__.py +0 -0
  21. gsc_mcp_tools-0.4.2/src/gsc_mcp/auth.py +108 -0
  22. gsc_mcp_tools-0.4.2/src/gsc_mcp/constants.py +24 -0
  23. gsc_mcp_tools-0.4.2/src/gsc_mcp/meta.py +11 -0
  24. gsc_mcp_tools-0.4.2/src/gsc_mcp/quota.py +20 -0
  25. gsc_mcp_tools-0.4.2/src/gsc_mcp/retry.py +36 -0
  26. gsc_mcp_tools-0.4.2/src/gsc_mcp/server.py +83 -0
  27. gsc_mcp_tools-0.4.2/src/gsc_mcp/tools/__init__.py +0 -0
  28. gsc_mcp_tools-0.4.2/src/gsc_mcp/tools/analytics.py +276 -0
  29. gsc_mcp_tools-0.4.2/src/gsc_mcp/tools/cross.py +218 -0
  30. gsc_mcp_tools-0.4.2/src/gsc_mcp/tools/crux.py +144 -0
  31. gsc_mcp_tools-0.4.2/src/gsc_mcp/tools/ga4.py +463 -0
  32. gsc_mcp_tools-0.4.2/src/gsc_mcp/tools/indexing.py +85 -0
  33. gsc_mcp_tools-0.4.2/src/gsc_mcp/tools/inspection.py +110 -0
  34. gsc_mcp_tools-0.4.2/src/gsc_mcp/tools/properties.py +80 -0
  35. gsc_mcp_tools-0.4.2/src/gsc_mcp/tools/seo.py +355 -0
  36. gsc_mcp_tools-0.4.2/src/gsc_mcp/tools/sitemaps.py +174 -0
  37. gsc_mcp_tools-0.4.2/tests/__init__.py +0 -0
  38. gsc_mcp_tools-0.4.2/tests/conftest.py +80 -0
  39. gsc_mcp_tools-0.4.2/tests/test_analytics.py +186 -0
  40. gsc_mcp_tools-0.4.2/tests/test_auth.py +70 -0
  41. gsc_mcp_tools-0.4.2/tests/test_cross.py +300 -0
  42. gsc_mcp_tools-0.4.2/tests/test_crux.py +171 -0
  43. gsc_mcp_tools-0.4.2/tests/test_ga4.py +474 -0
  44. gsc_mcp_tools-0.4.2/tests/test_indexing.py +108 -0
  45. gsc_mcp_tools-0.4.2/tests/test_inspection.py +81 -0
  46. gsc_mcp_tools-0.4.2/tests/test_properties.py +55 -0
  47. gsc_mcp_tools-0.4.2/tests/test_scaffold.py +23 -0
  48. gsc_mcp_tools-0.4.2/tests/test_seo.py +111 -0
  49. gsc_mcp_tools-0.4.2/tests/test_seo_v2.py +463 -0
  50. gsc_mcp_tools-0.4.2/tests/test_sitemap_audit.py +165 -0
  51. gsc_mcp_tools-0.4.2/tests/test_sitemaps.py +58 -0
  52. gsc_mcp_tools-0.4.2/tests/test_sitemaps_v2.py +156 -0
  53. gsc_mcp_tools-0.4.2/tests/test_utilities.py +114 -0
@@ -0,0 +1,53 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(rtk ls *)",
5
+ "Bash(rtk read *)",
6
+ "mcp__gsc-mcp__list_properties",
7
+ "mcp__gsc-mcp__list_sitemaps",
8
+ "mcp__gsc-mcp__get_performance_overview",
9
+ "mcp__gsc-mcp__check_alerts",
10
+ "mcp__gsc-mcp__batch_url_inspection",
11
+ "mcp__gsc-mcp__inspect_url",
12
+ "Bash(rtk pytest *)",
13
+ "Bash(echo \"---EXIT $?\")",
14
+ "Bash(rtk proxy *)",
15
+ "mcp__gsc-mcp__get_search_analytics",
16
+ "mcp__gsc-mcp__compare_search_periods",
17
+ "mcp__gsc-mcp__quick_wins",
18
+ "mcp__gsc-mcp__traffic_drops",
19
+ "mcp__gsc-mcp__submit_url",
20
+ "Bash(rtk git *)",
21
+ "Bash(git add *)",
22
+ "Bash(git commit -m ' *)",
23
+ "Bash(rtk pip *)",
24
+ "Bash(python -c \"from google.analytics.data_v1beta import BetaAnalyticsDataClient; print\\('GA4 lib OK'\\)\")",
25
+ "Bash(python -c \"from gsc_mcp.server import mcp; print\\('server import OK'\\)\")",
26
+ "Bash(rtk grep *)",
27
+ "Bash(python3 *)",
28
+ "Bash(printenv)",
29
+ "Bash(git remote *)",
30
+ "Bash(git config *)",
31
+ "Bash(hatch build *)",
32
+ "Bash(/tmp/test-gsc-mcp/bin/pip install *)",
33
+ "Bash(/tmp/test-gsc-mcp/bin/gsc-mcp --help)",
34
+ "Bash(/tmp/test-gsc-mcp/bin/python *)",
35
+ "Bash(rtk gh *)",
36
+ "Bash(gh repo *)",
37
+ "Bash(git tag *)",
38
+ "Bash(gh release *)",
39
+ "Bash(GSC_SERVICE_ACCOUNT_PATH=/Users/florianbruniaux/bel-etage-analytics-c4db99d1b930.json GSC_SKIP_OAUTH=true /Users/florianbruniaux/Sites/perso/google-search-console-mcp/.venv/bin/python -m gsc_mcp.server)",
40
+ "Bash(kill %1)",
41
+ "Bash(wait)",
42
+ "Bash(/Users/florianbruniaux/Sites/perso/google-search-console-mcp/.venv/bin/pip install *)",
43
+ "Bash(GSC_SERVICE_ACCOUNT_PATH=/Users/florianbruniaux/bel-etage-analytics-c4db99d1b930.json GSC_SKIP_OAUTH=true /Users/florianbruniaux/Sites/perso/google-search-console-mcp/.venv/bin/python *)",
44
+ "mcp__gsc-mcp__get_capabilities",
45
+ "mcp__gsc-mcp__traffic_health_check",
46
+ "Bash(.venv/bin/pytest tests/test_ga4.py tests/test_cross.py -v)",
47
+ "Bash(.venv/bin/pytest tests/ -q)",
48
+ "Bash(git commit *)",
49
+ "mcp__gsc-mcp__page_analysis",
50
+ "Bash(rtk find *)"
51
+ ]
52
+ }
53
+ }
@@ -0,0 +1,19 @@
1
+ # Google Search Console MCP — environment variables
2
+ # Copy to .env and fill in your values
3
+
4
+ # Path to your OAuth2 client credentials JSON (from Google Cloud Console)
5
+ # Required for OAuth flow (default auth method)
6
+ GSC_CREDENTIALS_PATH=/path/to/credentials.json
7
+
8
+ # Path to a Service Account JSON key file
9
+ # Alternative to OAuth — set this to skip the browser auth flow
10
+ # GSC_SERVICE_ACCOUNT_PATH=/path/to/service-account.json
11
+
12
+ # Skip OAuth flow entirely (requires GSC_SERVICE_ACCOUNT_PATH)
13
+ # GSC_SKIP_OAUTH=false
14
+
15
+ # Allow destructive operations (sitemap delete, etc.) — disabled by default
16
+ # GSC_ALLOW_DESTRUCTIVE=false
17
+
18
+ # Data state for search analytics: "final" or "all" (default: final)
19
+ # GSC_DATA_STATE=final
@@ -0,0 +1,13 @@
1
+ .env
2
+ *.pyc
3
+ __pycache__/
4
+ .venv/
5
+ dist/
6
+ *.egg-info/
7
+ .pytest_cache/
8
+ token.pickle
9
+ token_*.json
10
+ *client_secret*.json
11
+ *credentials*.json
12
+ *service_account*.json
13
+ .claudedocs/
@@ -0,0 +1,10 @@
1
+ # Default ignored files
2
+ /shelf/
3
+ /workspace.xml
4
+ # Editor-based HTTP Client requests
5
+ /httpRequests/
6
+ # Ignored default folder with query files
7
+ /queries/
8
+ # Datasource local storage ignored files
9
+ /dataSources/
10
+ /dataSources.local.xml
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="WEB_MODULE" version="4">
3
+ <component name="NewModuleRootManager">
4
+ <content url="file://$MODULE_DIR$" />
5
+ <orderEntry type="inheritedJdk" />
6
+ <orderEntry type="sourceFolder" forTests="false" />
7
+ </component>
8
+ </module>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/google-search-console-mcp.iml" filepath="$PROJECT_DIR$/.idea/google-search-console-mcp.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="" vcs="Git" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,97 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="AutoImportSettings">
4
+ <option name="autoReloadType" value="SELECTIVE" />
5
+ </component>
6
+ <component name="ChangeListManager">
7
+ <list default="true" id="463c5a88-8b49-4583-a93b-5bde7abb04de" name="Changes" comment="">
8
+ <change beforePath="$PROJECT_DIR$/.gitignore" beforeDir="false" afterPath="$PROJECT_DIR$/.gitignore" afterDir="false" />
9
+ <change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
10
+ <change beforePath="$PROJECT_DIR$/docs/architecture.md" beforeDir="false" afterPath="$PROJECT_DIR$/docs/architecture.md" afterDir="false" />
11
+ <change beforePath="$PROJECT_DIR$/src/gsc_mcp/auth.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/gsc_mcp/auth.py" afterDir="false" />
12
+ <change beforePath="$PROJECT_DIR$/src/gsc_mcp/retry.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/gsc_mcp/retry.py" afterDir="false" />
13
+ <change beforePath="$PROJECT_DIR$/src/gsc_mcp/tools/analytics.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/gsc_mcp/tools/analytics.py" afterDir="false" />
14
+ <change beforePath="$PROJECT_DIR$/src/gsc_mcp/tools/ga4.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/gsc_mcp/tools/ga4.py" afterDir="false" />
15
+ <change beforePath="$PROJECT_DIR$/src/gsc_mcp/tools/indexing.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/gsc_mcp/tools/indexing.py" afterDir="false" />
16
+ <change beforePath="$PROJECT_DIR$/src/gsc_mcp/tools/inspection.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/gsc_mcp/tools/inspection.py" afterDir="false" />
17
+ <change beforePath="$PROJECT_DIR$/src/gsc_mcp/tools/properties.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/gsc_mcp/tools/properties.py" afterDir="false" />
18
+ <change beforePath="$PROJECT_DIR$/src/gsc_mcp/tools/seo.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/gsc_mcp/tools/seo.py" afterDir="false" />
19
+ <change beforePath="$PROJECT_DIR$/src/gsc_mcp/tools/sitemaps.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/gsc_mcp/tools/sitemaps.py" afterDir="false" />
20
+ </list>
21
+ <option name="SHOW_DIALOG" value="false" />
22
+ <option name="HIGHLIGHT_CONFLICTS" value="true" />
23
+ <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
24
+ <option name="LAST_RESOLUTION" value="IGNORE" />
25
+ </component>
26
+ <component name="CopilotPersistence">
27
+ <persistenceIdMap>
28
+ <entry key="_/Users/florianbruniaux/Sites/perso/google-search-console-mcp" value="3FP6uMgTYPCWF1rcWtp89wMwXqi" />
29
+ </persistenceIdMap>
30
+ </component>
31
+ <component name="EmbeddingIndexingInfo">
32
+ <option name="cachedIndexableFilesCount" value="3722" />
33
+ <option name="fileBasedEmbeddingIndicesEnabled" value="true" />
34
+ </component>
35
+ <component name="Git.Settings">
36
+ <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
37
+ </component>
38
+ <component name="McpProjectServerCommands">
39
+ <commands />
40
+ <urls />
41
+ </component>
42
+ <component name="ProjectColorInfo">{
43
+ &quot;associatedIndex&quot;: 7,
44
+ &quot;fromUser&quot;: false
45
+ }</component>
46
+ <component name="ProjectId" id="3FP6uMgTYPCWF1rcWtp89wMwXqi" />
47
+ <component name="ProjectViewState">
48
+ <option name="hideEmptyMiddlePackages" value="true" />
49
+ <option name="showLibraryContents" value="true" />
50
+ </component>
51
+ <component name="PropertiesComponent">{
52
+ &quot;keyToString&quot;: {
53
+ &quot;ModuleVcsDetector.initialDetectionPerformed&quot;: &quot;true&quot;,
54
+ &quot;RunOnceActivity.MCP Project settings loaded&quot;: &quot;true&quot;,
55
+ &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
56
+ &quot;RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252&quot;: &quot;true&quot;,
57
+ &quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
58
+ &quot;RunOnceActivity.typescript.service.memoryLimit.init&quot;: &quot;true&quot;,
59
+ &quot;codeWithMe.voiceChat.enabledByDefault&quot;: &quot;false&quot;,
60
+ &quot;com.intellij.ml.llm.matterhorn.ej.ui.settings.DefaultModelSelectionForGA.v1&quot;: &quot;true&quot;,
61
+ &quot;git-widget-placeholder&quot;: &quot;main&quot;,
62
+ &quot;javascript.preferred.runtime.type.id&quot;: &quot;node&quot;,
63
+ &quot;junie.onboarding.icon.badge.shown&quot;: &quot;true&quot;,
64
+ &quot;last_opened_file_path&quot;: &quot;/Users/florianbruniaux/Sites/perso/google-search-console-mcp&quot;,
65
+ &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
66
+ &quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
67
+ &quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
68
+ &quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
69
+ &quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
70
+ &quot;to.speed.mode.migration.done&quot;: &quot;true&quot;,
71
+ &quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
72
+ }
73
+ }</component>
74
+ <component name="SharedIndexes">
75
+ <attachedChunks>
76
+ <set>
77
+ <option value="bundled-js-predefined-d6986cc7102b-31caf2ab9e3c-JavaScript-WS-261.25134.101" />
78
+ </set>
79
+ </attachedChunks>
80
+ </component>
81
+ <component name="TaskManager">
82
+ <task active="true" id="Default" summary="Default task">
83
+ <changelist id="463c5a88-8b49-4583-a93b-5bde7abb04de" name="Changes" comment="" />
84
+ <created>1781964578952</created>
85
+ <option name="number" value="Default" />
86
+ <option name="presentableId" value="Default" />
87
+ <updated>1781964578952</updated>
88
+ <workItem from="1781964579955" duration="2110000" />
89
+ <workItem from="1782057449022" duration="7333000" />
90
+ <workItem from="1782214079804" duration="3000" />
91
+ </task>
92
+ <servers />
93
+ </component>
94
+ <component name="TypeScriptGeneratedFilesManager">
95
+ <option name="version" value="3" />
96
+ </component>
97
+ </project>
@@ -0,0 +1 @@
1
+ 3.13.0
@@ -0,0 +1,17 @@
1
+ # gsc-mcp v0.5.0 SDD Progress
2
+
3
+ Branch: main
4
+ Started: 2026-06-23
5
+ MERGE_BASE: 587c08cee (to be confirmed at start of each review)
6
+
7
+ ## Tasks
8
+
9
+ - [ ] Task 1: Phase 1 - GA4 hostname + country filters
10
+ - [ ] Task 2: Phase 2 - CrUX tools (crux_page_vitals, crux_history)
11
+ - [ ] Task 3: Phase 3 - sitemap_audit tool
12
+ - [ ] Task 4: Phase 4 - schema_validate tool
13
+ - [ ] Task 5: Phase 5 - version bump + capabilities + docs
14
+
15
+ ## Log
16
+
17
+ (entries appended as tasks complete)
@@ -0,0 +1,101 @@
1
+ # Changelog
2
+
3
+ ## [0.4.2] - 2026-06-22
4
+
5
+ ### Added
6
+
7
+ - 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 optionnel `property_id: str = None`. Quand fourni, il override `GA4_PROPERTY_ID` sans modifier la config. Permet de requêter plusieurs properties GA4 depuis une seule instance MCP.
8
+ - `get_ga4_property_id(override=None)` dans `auth.py` : accepte un override direct, court-circuite la lecture de l'env var. Sans override, comportement identique à avant.
9
+ - 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`
10
+
11
+ ## [0.4.1] - 2026-06-22
12
+
13
+ Corrections de cohérence et consolidation interne : pas de nouveaux tools.
14
+
15
+ ### Fixed
16
+
17
+ - `get_capabilities` retournait 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és
18
+ - `inspect_url`, `batch_url_inspection`, `check_indexing_issues` appelaient `webmasters/v3`, qui n'expose pas `urlInspection`. Ces trois tools levaient une `AttributeError` au runtime sur chaque appel. Corrigé en passant sur `searchconsole/v1` (API qui expose la ressource `urlInspection`)
19
+ - `traffic_health_check` et `page_analysis` incluent maintenant un champ `note` dans leur réponse JSON pour avertir que GSC a un décalage de 3 jours vs GA4, les ratios sont donc approximatifs
20
+ - `ga4_organic_landing_pages` ajoute un champ `note` quand le nombre de résultats atteint la limite, signalant une troncature potentielle
21
+
22
+ ### Changed
23
+
24
+ - Tous les tools GSC (analytics, SEO, sitemaps, properties) consolidés sur `searchconsole/v1`. Le client `webmasters/v3` (`get_gsc_service`) est supprimé de `auth.py`. `searchconsole/v1` expose les mêmes ressources `sites`, `searchanalytics`, `sitemaps` en plus de `urlInspection`
25
+
26
+ ### Tests
27
+
28
+ - Couverture `quick_wins` amé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
29
+
30
+ ## [0.4.0] - 2026-06-22
31
+
32
+ Phase 3: 2 tools cross-platform GSC+GA4, nouveau module `cross.py`.
33
+
34
+ ### Added
35
+
36
+ - `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 parmi `no_gsc_data`, `tracking_gap` (ratio < 0.6), `filter_issue` (ratio > 1.3) ou `healthy`. GA4 interrogé avec `limit=10000` pour éviter les sous-comptages sur les gros sites
37
+ - `page_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 un `opportunity_score = log10(impressions+1)*10 + engagement_rate*100 + log10(conversions+1)*20`, trié décroissant, tronqué à `limit`
38
+ - `_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 jointure
39
+ - `engagement_rate` dérivé dans `cross.py` comme `engaged_sessions/sessions` (formule GA4 native), sans modifier la sortie de `ga4_organic_landing_pages`
40
+ - 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
41
+
42
+ ### Changed
43
+
44
+ - Tool count: 30 vers 32
45
+
46
+ ## [0.3.0] - 2026-06-22
47
+
48
+ Phase 2: 6 new GA4 tools, new dependency, new environment variable.
49
+
50
+ ### Added
51
+
52
+ - `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 the `sessionMedium=organic` filter on `landingPagePlusQueryString`
53
+ - `ga4_traffic_sources(start_date, end_date)`: sessions and conversions broken down by channel group, source and medium
54
+ - `ga4_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). Optional `page_path` parameter adds a CONTAINS filter
55
+ - `ga4_realtime()`: active users right now, by screen name, country and device. No date range, uses `run_realtime_report` directly
56
+ - `ga4_user_behavior(start_date, end_date)`: single `batch_run_reports` call returning three breakdowns (by device, by country top 20, by user type new/returning)
57
+ - `ga4_conversion_funnel(start_date, end_date, event_name)`: two sequential `run_report` calls. First lists pages with conversions > 0; second lists events, optionally filtered by exact event name
58
+ - New dependency: `google-analytics-data>=0.18.0` (Google Analytics Data API v1beta, protobuf-based client)
59
+ - 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
60
+ - `get_ga4_service()` and `get_ga4_property_id()` in `auth.py`, reusing the same `_resolve_creds` path as GSC and Indexing clients
61
+
62
+ ### Changed
63
+
64
+ - Tool count: 24 → 30
65
+
66
+ ## [0.2.0] - 2026-06-22
67
+
68
+ Phase 1: 6 new tools, no new dependencies.
69
+
70
+ ### Added
71
+
72
+ - `seo_striking_distance(site, days, min_impressions)`: queries in positions 8-15 sorted by impressions desc. Separate band from `quick_wins` (4-15), intended for queries one push away from page 1
73
+ - `seo_cannibalization(site, days, min_impressions)`: detects queries split across multiple pages using an HHI conflict score (`1 - sum(share²)`). Zero-click groups use uniform `1/n` fallback to avoid division by zero. Filters on per-query total impressions, not per-page
74
+ - `seo_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 as `traffic_drops`
75
+ - `analytics_anomalies(site, days, threshold)`: Z-score anomaly detection on daily clicks via `statistics.pstdev`. Returns `anomalies = []` when std is zero (constant or all-zero series) to handle low-traffic sites safely
76
+ - `sitemaps_delete(site, sitemap_url)`: deletes a sitemap with a safety check before any API call (URL must end with `.xml` or contain `/sitemap`, raises `ValueError` otherwise)
77
+ - `sitemaps_get(site, sitemap_url)`: fetches a single sitemap resource and normalises it to the same flat shape as `list_sitemaps` (warnings and errors coerced to int)
78
+ - 62 new tests (35 SEO, 15 sitemaps, 9 analytics anomalies): all mocked, no API calls
79
+
80
+ ### Changed
81
+
82
+ - Tool count: 18 → 24
83
+
84
+ ## [0.1.0] - 2026-06-20
85
+
86
+ Initial release.
87
+
88
+ ### Added
89
+
90
+ - 18 MCP tools across 6 categories: meta, properties, analytics, SEO, inspection, indexing, sitemaps
91
+ - `submit_batch` using true HTTP multipart batch via `new_batch_http_request()`, chunked at 100 URLs per request. Avoids the late-binding closure bug with a `_make_callback(url)` factory pattern
92
+ - Dual OAuth scope architecture: separate clients for GSC (`auth/webmasters`) and Indexing API (`auth/indexing`), because the Indexing API rejects webmasters tokens
93
+ - OAuth flow with token stored as JSON (`google.oauth2.credentials.Credentials.to_json()`) instead of pickle
94
+ - Service Account support as an alternative to OAuth (set `GSC_SERVICE_ACCOUNT_PATH`)
95
+ - Exponential backoff retry on HTTP 429/500/502/503/504 via `with_retry()` decorator, no retry on 404
96
+ - In-memory quota tracker (`QuotaTracker`) with configurable limit and warn threshold (warns at 180/200 by default)
97
+ - `with_meta()` wrapper on all tool outputs: every response includes a `_meta` block with tool name and call parameters, so Claude has full context on what was fetched
98
+ - `quick_wins` tool scoring CTR opportunity vs benchmark by SERP position
99
+ - `check_indexing_issues` categorizing URLs into `not_indexed`, `robots_blocked`, `fetch_error`, `canonical_issue`, `indexed`
100
+ - `traffic_drops` diagnosing drops as `ranking_loss`, `ctr_collapse`, or `demand_decline`
101
+ - Full test suite: 52 tests, fully mocked, no Google API calls required
@@ -0,0 +1,62 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Commands
6
+
7
+ ```bash
8
+ # Requires Python 3.11+
9
+ python3 --version # must be 3.11 or higher
10
+
11
+ # Install (dev mode with test deps)
12
+ pip install -e ".[dev]"
13
+
14
+ # Run the MCP server
15
+ gsc-mcp
16
+ # or
17
+ python -m gsc_mcp.server
18
+
19
+ # Run all tests
20
+ pytest tests/ -v
21
+
22
+ # Run a single test file
23
+ pytest tests/test_analytics.py -v
24
+
25
+ # Run a specific test by name
26
+ pytest tests/ -k "test_submit_batch" -v
27
+ ```
28
+
29
+ ## Architecture
30
+
31
+ **Entry point**: `src/gsc_mcp/server.py` creates a `FastMCP("gsc-mcp")` instance and registers all 32 tools via `mcp.tool()()`. No dynamic discovery; every tool is explicitly imported and registered here. Adding a new tool means: implement it in the relevant `tools/` module, import it in `server.py`, and register it.
32
+
33
+ **Auth layer** (`auth.py`): Three separate credential pairs: GSC API (`searchconsole/v1`), Indexing API (`indexing/v3`), and GA4 (`analytics.readonly`). Resolution order: if `GSC_SERVICE_ACCOUNT_PATH` is set, use service account credentials. Otherwise, fall through to OAuth with token cached as JSON (not pickle) at the OS user data dir (`~/Library/Application Support/gsc-mcp/` on macOS). Token files: `token_gsc.json`, `token_indexing.json`, `token_ga4.json`. `get_ga4_property_id(override=None)` accepts an optional override string that bypasses `GA4_PROPERTY_ID`, enabling per-call multi-property support.
34
+
35
+ **Tools** (`src/gsc_mcp/tools/`): Eight modules, each owns a logical domain (analytics, cross, ga4, indexing, inspection, properties, seo, sitemaps). Every tool function is a plain Python function (no class, no decorator) registered by `server.py`. All return JSON strings via `json.dumps(with_meta(...))`.
36
+
37
+ **Cross-platform pattern** (`cross.py`): `traffic_health_check` and `page_analysis` compose the high-level functions from `analytics.py` and `ga4.py`. They call those functions, parse their JSON strings with `json.loads`, then join on `_normalize_url` (strips scheme/host/query/trailing-slash so GSC absolute URLs and GA4 paths match). `engagement_rate` is derived as `engaged_sessions/sessions` because `ga4_organic_landing_pages` does not expose it directly.
38
+
39
+ **Output contract** (`meta.py`): Every tool wraps its response dict with `with_meta(data, tool=..., params=...)`, which appends a `_meta` block (`{"tool": "<name>", "params": {...}}`). Any new tool must follow this pattern.
40
+
41
+ **Retry** (`retry.py`): `@with_retry(max_retries=3, base_delay=1.0)` wraps Google API calls. Retries on 429/500/502/503/504 with exponential backoff (`base_delay * 2^attempt`). Other HTTP errors propagate immediately.
42
+
43
+ **Quota tracking** (`quota.py` + `indexing.py`): `QuotaTracker` is a module-level singleton in `indexing.py` that tracks Indexing API usage within a single process lifetime (200 req/day limit, warns at 180). It does not persist across restarts; restarting the server resets the counter.
44
+
45
+ **Batching** (`indexing.py`): `submit_batch` uses `svc.new_batch_http_request()` chunked at 100 URLs per HTTP request. This is a true multipart batch, not a sequential loop. Callbacks collect per-URL results.
46
+
47
+ ## Test conventions
48
+
49
+ All tests are fully mocked (no real Google API calls). `tests/conftest.py` defines two shared fixtures:
50
+ - `mock_gsc_service`: MagicMock wired for `sites`, `searchanalytics`, `sitemaps`, `urlInspection`
51
+ - `mock_indexing_service`: MagicMock with a working `new_batch_http_request()` implementation that fires callbacks synchronously
52
+
53
+ Tests patch `get_searchconsole_service` / `get_indexing_service` / `get_ga4_service` at the call site (e.g., `gsc_mcp.tools.analytics.get_searchconsole_service`) and inject the fixture as the return value. GA4 tests also use an `autouse` fixture to set `GA4_PROPERTY_ID`, since `get_ga4_property_id()` raises `RuntimeError` if the env var is absent and no `override` is passed.
54
+
55
+ ## Environment variables
56
+
57
+ | Variable | Purpose |
58
+ |---|---|
59
+ | `GSC_SERVICE_ACCOUNT_PATH` | Path to service account JSON (preferred for automation) |
60
+ | `GSC_CREDENTIALS_PATH` | Path to OAuth Desktop client JSON (interactive OAuth flow) |
61
+ | `GSC_SKIP_OAUTH` | Set to `true` to disable OAuth fallback (requires SA path) |
62
+ | `GA4_PROPERTY_ID` | Numeric GA4 property ID (e.g. `123456789`). Required for GA4 tools only, validated lazily |
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Florian Bruniaux
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.