{"$schema":"https://static.modelcontextprotocol.io/schemas/mcp-server-card/v1.json","version":"1.0","protocolVersion":"2025-11-25","supportedProtocolVersions":["2025-11-25","2025-06-18","2025-03-26","2024-11-05","2024-10-07"],"serverInfo":{"name":"gridar-mcp","title":"Gridar","version":"0.7.0"},"description":"SEO toolkit for Quebec sites: audits, Google.ca rankings, keywords, AI-search visibility.","iconUrl":"https://www.gridar.app/brand/gridar-mark-512.png","documentationUrl":"https://www.gridar.app/mcp","websiteUrl":"https://www.gridar.app","transport":{"type":"streamable-http","endpoint":"/mcp"},"capabilities":{"tools":{}},"authentication":{"required":true,"schemes":["bearer","oauth2"]},"tools":[{"name":"gridar_get_me","description":"Return the authenticated user's plan, current monthly article quota and remaining usage."},{"name":"gridar_list_sites","description":"List the sites belonging to the user (id, domain, hosting mode, default language)."},{"name":"gridar_list_articles","description":"List articles on a given site. Optional filters: status, language, limit."},{"name":"gridar_get_article","description":"Return the full content (HTML + metadata) of a single article identified by site_id and slug."},{"name":"gridar_generate_article","description":"Generate a new SEO-optimized article on the given site. Consumes one article from the user's monthly quota (or one credit). Returns the generated content and the new total post count."},{"name":"gridar_generate_sister_article","description":"Translate + adapt an existing article into another language and publish it as a sibling sharing the same translation_group (so hreflang alternates wire up automatically). Costs one article from the user's monthly quota (or one credit). Only available on hosted-mode sites. The new post is created with status='draft' for review."},{"name":"gridar_audit_article","description":"Run the SEO auditor on raw content. Returns a 0-100 score plus actionable suggestions (length, keyword density, headings, links, meta)."},{"name":"gridar_get_brief","description":"Build a content brief for a target keyword: search intent, outline, entities to cover, FAQ candidates, E-E-A-T signals."},{"name":"gridar_list_keywords","description":"List the tracked keywords for a site with their latest recorded ranking."},{"name":"gridar_snapshot_keywords","description":"Trigger a fresh ranking snapshot for all tracked keywords on a site. Returns the count of positions recorded."},{"name":"gridar_weekly_digest","description":"Return the weekly digest for a site (traffic, ranking, content decay) over the last 7 days."},{"name":"gridar_get_site","description":"Return full configuration of one site: integrations status, default language, knowledge base, competitors, GSC connection, autopilot config, author E-E-A-T fields."},{"name":"gridar_update_site","description":"Update editable site fields (name, description, knowledge_base, competitors, default_language, author_bio, author_credentials, public_blog_domain). Pass only the fields you want to change."},{"name":"gridar_create_article","description":"Create a manually-authored article on a hosted site (no AI generation, no quota consumed). Use this when the user already has content to publish via the dashboard."},{"name":"gridar_update_article","description":"Patch fields of an existing article (title, content, excerpt, status, cover_image, author, slug). Works on all site modes: hosted, external (Postgres), WordPress, Shopify, and Webflow. The CMS adapter is selected automatically from the site's mode. Status=published auto-sets published_at on hosted/external; CMS-specific status mapping happens for WP/Shopify/Webflow."},{"name":"gridar_delete_article","description":"DESTRUCTIVE: permanently delete an article. Works on all site modes: hosted, external, WordPress, Shopify, and Webflow. CMS-side deletes are forced (no trash). Cascades attribution rows on hosted."},{"name":"gridar_track_keyword","description":"Add a keyword to track on a site. SERP position is snapshotted daily by the rank_snapshot cron. Reactivates the row if it was previously soft-deleted."},{"name":"gridar_untrack_keyword","description":"Soft-delete a tracked keyword (sets is_active=False). Historical SerpRank snapshots are preserved."},{"name":"gridar_suggest_keywords","description":"Ask Claude to suggest new tracked keywords for the site, anchored on RAG memory + homepage scrape (not just brand name)."},{"name":"gridar_suggest_competitors","description":"Auto-detect likely SEO competitors for the site based on homepage + tracked keywords."},{"name":"gridar_analyze_competitors","description":"Analyze the SERP top 10 for a keyword: average word count, common headings, entity coverage, gap analysis."},{"name":"gridar_serp_analyze","description":"SERP Analyzer: fetch the top 10 organic results for a keyword and compute per-URL metrics (word count, h1/h2/h3 count, FAQ presence, video embed) plus averages. Lightweight Surfer-style benchmark used to size a competing article."},{"name":"gridar_ai_visibility_summary","description":"AI Visibility tracker: returns aggregated metrics describing how often the site is cited in LLM responses (ChatGPT, Perplexity, Gemini, SearchGPT, Claude). Output: {score (weighted 0-100), total_mentions, cited_pages_count, total_checks, total_prompts, breakdown_by_engine: {engine: {total_checks, cited_count, cite_rate}}, delta_7d}. Use this to show users their AI-search visibility (the next-gen SEO metric)."},{"name":"gridar_run_ai_visibility","description":"Trigger an AI Visibility check series for a site's tracked prompts. Creates one AiVisibilityResult per (prompt, engine) pair across all 5 engines (ChatGPT, Perplexity, Gemini, SearchGPT, Claude). V1: results are mocked plausibly so the user can validate the UI before external LLM APIs are wired. Pass prompt_id to re-check only a single prompt."},{"name":"gridar_get_content_decay","description":"Find articles whose GSC impressions / clicks dropped significantly over the last N days. Suggests refresh / expand / redirect actions."},{"name":"gridar_find_broken_links","description":"Crawl the site articles + check outgoing links. Returns list of 4xx/5xx/timeouts."},{"name":"gridar_check_hreflang","description":"Validate hreflang tags on a site (translation_group consistency) OR on a raw HTML payload."},{"name":"gridar_detect_cannibalization","description":"Find pairs of articles on a site that compete for the same intent / keywords. Cross-language and cross-status aware."},{"name":"gridar_suggest_internal_links","description":"AI suggestions for internal-link insertions on a target article, anchored on the site's content corpus."},{"name":"gridar_bulk_audit","description":"Run the SEO audit across ALL published articles of a site in parallel (Gemini). Returns aggregated scores + top issues. Can take 30-60s on large sites."},{"name":"gridar_check_readability","description":"Score content readability (Flesch-Kincaid, ARI) in FR or EN."},{"name":"gridar_check_plagiarism","description":"Run originality / plagiarism check on the content (Tier 4 plan)."},{"name":"gridar_keyword_research","description":"Expand a seed keyword into related queries. Sources: Serper Google SERP (relatedSearches, peopleAlsoAsk) plus Gemini long-tail variants, merged and deduplicated, the seed itself removed. Locale follows `language`; fr maps to Google Canada. Returns {keywords: [{keyword, source, estimated_intent, family_id, family_size, family_primary}]}. `estimated_intent` is guessed from words present in the keyword text, it is not measured on the SERP. `family_*` links rewordings of one query: same family_id means the same demand phrased differently, family_primary marks the wording to target. This tool returns NO search volume and NO keyword difficulty, because nothing behind it measures either one: never report, derive or estimate those numbers from this output. Also returns `sources`, the state of each provider for this call ('ok', 'aucun_resultat', 'echec', 'non_configure', 'delai_depasse') with counts: read it before judging the list, because a short list can mean Google served no related-searches block for this query rather than no such keywords existing. Report a failed or empty provider instead of presenting the remaining keywords as the whole picture."},{"name":"gridar_page_speed","description":"Run PageSpeed Insights on a URL. Returns Core Web Vitals + suggestions."},{"name":"gridar_people_also_ask","description":"Harvest People-Also-Ask questions from Google SERP for a keyword. Useful to seed FAQ schemas."},{"name":"gridar_link_opportunities","description":"Surface candidate sites for backlink outreach on a keyword. Queries Serper for 'best <keyword> sites' + resources / blogs variants, de-duplicates by domain, filters out social/marketplace giants, and returns the top targets (domain + url + title + snippet + source_query)."},{"name":"gridar_gsc_queries","description":"Pull top GSC queries (impressions / clicks / position) for a single article page over the last N days. Requires the article `slug`; the site must have GSC connected."},{"name":"gridar_get_autopilot","description":"Return autopilot config for a site: enabled flag, weekly_count, last_run_at, next_run_at, last_error."},{"name":"gridar_set_autopilot","description":"Configure autopilot. Pass only the fields you want to change."},{"name":"gridar_run_autopilot_now","description":"Manually trigger one autopilot generation cycle for a site, ignoring the cron schedule. Useful for debugging or one-off generation."},{"name":"gridar_list_memories","description":"List the site's RAG memory chunks (articles + KB + manual notes + audit decisions). Paginated."},{"name":"gridar_add_memory","description":"Add a manual note to a site's RAG memory. 'manual' and 'decision' kinds carry stronger instruction weight in future generations."},{"name":"gridar_delete_memory","description":"DESTRUCTIVE: delete a memory chunk from a site's RAG."},{"name":"gridar_rebuild_memory","description":"Re-chunk and re-embed all published articles + KB into the RAG memory. Costs Voyage AI tokens (~$0.001 per 100 chunks)."},{"name":"gridar_proof_summary","description":"Aggregated site-level proof of value: total impressions gained, clicks gained, top 5 article gainers."},{"name":"gridar_proof_attribution","description":"List per-article attribution rows (J+30 / J+60 / J+90 snapshots vs baseline). Optionally filter by post_id."},{"name":"gridar_enable_proof_share","description":"Enable the public proof page for a site. Returns a public_url that anyone can visit (read-only). Pass rotate=true to revoke the previous token."},{"name":"gridar_revoke_proof_share","description":"Disable the public proof page. The URL returns 404 immediately. Historical attribution data is preserved internally."},{"name":"gridar_classify_intent","description":"Classify the search intent of a keyword (transactional, commercial, informational, navigational, local) with a confidence score. Step 1 of the topic cluster workflow. Pass site_id for brand-aware detection."},{"name":"gridar_propose_keyword_strategy","description":"Propose a site structure for a target keyword: classifies intent, analyzes the SERP, then proposes single-landing OR pillar+cluster OR comparison-table OR local-landing OR blog post. Returns a KeywordStrategy.id the user can approve. Idempotent on (site, keyword): re-running refreshes a draft, never overwrites an approved strategy."},{"name":"gridar_approve_strategy","description":"Approve a proposed keyword strategy and generate every proposed_page. Landings are AI-generated and saved as drafts; articles are queued (the user runs gridar_generate_article on each one to control quota). Pass customizations.pages to override the proposed pages list before generation."},{"name":"gridar_create_landing","description":"Create a structured landing page manually (no AI, no quota). Body is composed of structured blocks: hero (h1 + subtitle + CTA), value_props, social_proof, FAQ, bottom CTA. Use this when the user already has the copy."},{"name":"gridar_generate_landing","description":"AI-generate a complete landing page payload from a target keyword (H1, hero subtitle, 3-5 value props, social proof, 4-6 FAQ, bottom CTA, body markdown). Consumes one article from the user's monthly quota (or one credit). Saved as draft on the hosted site."},{"name":"gridar_link_pages","description":"Insert an inline contextual link from one page to another on the same site. Works between landings, articles, or any mix. If the anchor text is found in the source body it's converted in-place; otherwise a 'Voir aussi' line is appended. No-op when already linked. Use to build topic cluster internal linking."},{"name":"gridar_site_seo_score","description":"Composite SEO health score (0-100) for a site, computed from 9 weighted checks: indexability, author bio, business model declared, GSC connection, presence of an approved pillar+spokes cluster, hreflang validity (if multilingual), Core Web Vitals, FAQ schema coverage, and absence of transactional intents stuck on blog posts. Returns {score, breakdown, action_items: [{issue, fix_url}]} so the UI / agent can surface concrete next steps."},{"name":"gridar_ai_overview_readiness","description":"Score how AI-Overview-ready a single article is (0-100). Computed from 5 weighted checks: FAQ density (25%), H2/H3 structure (20%), E-E-A-T signals - author bio + sources/dates (20%), concrete data - numbers + dates (20%), and extractible quotes - short paragraphs right after ## headings (15%). Returns {score, breakdown, suggestions: string[]} so the agent can advise the user where to improve the article for Google SGE / Bing Copilot / Perplexity pickup."},{"name":"gridar_check_renderability","description":"Fetch a URL and detect whether it's server-rendered (SSR/SSG/prerendered) or a JavaScript SPA shell that Googlebot has to render itself. Inspects the raw HTML body and counts visible text words after stripping scripts and styles - >100 words = OK, less = suspect SPA. Returns {url, is_prerendered, html_contains_main_text, word_count, warning, suggestion}. Use this to quickly tell a user whether their site is Googlebot-friendly or needs prerendering / SSR (Next.js, Nuxt, Astro, prerender.io)."},{"name":"gridar_list_strategic_opportunities","description":"List the cached strategic opportunities for a site. Each one ties a tracked keyword (transactional / commercial / local intent, no landing yet) to a structured page concept: page_type (tool_landing | service_page | comparison | guide_pillar | quiz_landing | calculator | local_landing | lead_magnet), suggested_title, suggested_url_path, the hook (the specific thing that gets the click), capture_mechanism (how the lead is captured), conversion_path (how the lead becomes a customer), the angle vs the current SERP top 5, asset_match (whether the site already has the product/feature to power the hook), asset_used name, why_this_works rationale, and a priority bucket. Each opportunity also carries a quantitative estimate: monthly_volume (Serper), estimated_visits at SERP rank 5 (CTR table * volume), estimated_leads_range and estimated_paid_range computed from the site's business_model conversion table. Status flow: proposed (waiting) -> approved (landing being generated) -> done (HostedLanding live). Call gridar_refresh_strategic_opportunities first to populate, then this to enumerate."},{"name":"gridar_refresh_strategic_opportunities","description":"Run the strategic opportunity engine for a site. For each tracked keyword whose intent is non-info (transactional / commercial / local) AND that doesn't already have a matching landing, the engine builds a structured Claude prompt with the site's business_model + description + knowledge_base + CTA + SERP top 5 + existing pages list + Niveau-2 quantitative estimate, then asks Claude to propose ONE page that capitalises on the site's existing assets to capture and convert that keyword's intent. Each accepted concept is upserted as a StrategicOpportunity row. Bounded by max_keywords (default 8, cap 20) to keep Claude cost predictable. Idempotent: re-running refreshes concepts, preserves dismissed / done statuses. Returns counts + per-keyword refresh status."},{"name":"gridar_action_strategic_opportunity","description":"Take action on a single strategic opportunity. 'approve' triggers the LandingGenerator to AI-generate a full HostedLanding from the concept (mapping page_type -> page_subtype, honoring the suggested_url_path slug, dedup-ing against existing slugs) and marks the opportunity done with a forward link to the new landing. 'dismiss' hides the opportunity from the proposed list (won't reappear on refresh until restored). 'restore' un-dismisses. Use approve as the natural 'just go build it' command when an opportunity looks good."},{"name":"gridar_export_page_prompt","description":"Export a ready-to-paste prompt for the USER'S OWN AI coding assistant (ChatGPT, Lovable, v0, Bolt, Copilot, Gemini...). This is the delivery channel for clients who build their site with an AI tool and have no CMS Gridar can push into - their AI does the integration in their codebase, Gridar never touches their repo. Two modes: pass landing_id for an INTEGRATE prompt (the copy already exists and was keyword-calibrated - the prompt instructs the target AI to reproduce it verbatim, with exact slug, SEO meta, section structure and both JSON-LD blocks to inject as-is); or pass opportunity_id for a CREATE prompt (only the strategic concept exists - the prompt instructs the target AI to write the copy following the binding brief: hook, capture mechanism, conversion path, angle, FR-CA Quebec lexicon when the content language is French). Optional `stack` adapts the final integration instructions (nextjs, react, html, astro, wordpress, webflow, generic). Pure server-side templating: no LLM call, no quota consumed, instant. Returns {prompt, source, stack, char_count}. Typical flow: gridar_list_strategic_opportunities -> user picks one -> gridar_export_page_prompt(opportunity_id) -> hand the prompt text to the user to paste in their AI tool."},{"name":"gridar_pull_build_queue","description":"Pull the QUEUE of pages a coding agent should create in the client's OWN repo, for a dev-built / external site Gridar cannot publish to directly (e.g. a custom Next.js app). Every entry carries its own `kind` and `id` - echo those back to gridar_mark_page_built. `landings` (kind='landing') = page briefs from strategic opportunities: write the copy from `build_prompt` (C1). `articles` hold two kinds: kind='article' = GENERATED COPY (finished keyword-calibrated title/slug/excerpt/content) you integrate VERBATIM (C2); kind='article_brief' = an informational-article brief (from an info-intent tracked keyword) you write the article from, a fallback when Gridar has no generated copy yet. Pure read, no LLM call, no quota. Optional `stack` (nextjs, react, html, astro, wordpress, webflow, generic) adapts the build prompts. Loop: gridar_pull_build_queue -> build each page -> gridar_mark_page_built with that entry's kind + id + the live url. Returns {delivery_mode, landings, articles, counts, note}."},{"name":"gridar_mark_page_built","description":"Report that a queued page from gridar_pull_build_queue is now LIVE in the client's site, so Gridar can close the delivery loop. Echo the queue entry's own `kind` and `id`, plus the live `url`. kind='landing' (id=opportunity_id) marks the opportunity done so it archives out. kind='article' (id=post_id, a generated-copy article) stamps the delivered URL so the proof loop can attribute positions. kind='article_brief' (id=tracked_keyword_id, an article you wrote from a brief) stamps the keyword's target URL, dropping it from the queue and feeding rank tracking. Call it once per page after you deploy it."},{"name":"gridar_cluster_map","description":"See the site's TOPIC CLUSTERS: groups the site's known keywords (tracked keywords + strategic opportunities) into clusters, each with one `pillar` (the broad topic page) and its `spokes` (supporting subtopics). Each keyword carries `covered` (true if a page already exists for it) and each cluster carries `missing` (how many pages are still to build). Pure read, deterministic, no LLM call, no quota. Use it to plan topic authority, then build a cluster with gridar_build_cluster. Returns {clusters, total_keywords, clustered, unclustered}."},{"name":"gridar_build_cluster","description":"Build a whole TOPIC CLUSTER at once: returns the coordinated set of build prompts for the `pillar` page + each `spoke` article, with the INTERNAL MESH baked into every prompt (the pillar links to every spoke, each spoke links back to the pillar). Pass `pillar_keyword` and the `spokes` keywords (e.g. from gridar_cluster_map). Each returned page carries kind='article_brief' and its `id` (tracked_keyword_id): build it in the repo, then report it with gridar_mark_page_built(kind='article_brief', id, url). No LLM call here, no quota - YOU (the agent) write the copy from each prompt. Optional `stack` adapts the prompts. Returns {pillar, spokes, counts, note}."},{"name":"gridar_index_coverage","description":"Check which of a site's pages Google has actually INDEXED. Cross-references the site's expected pages (its sitemap.xml, or the pages Gridar knows about) against `site:<domain>` (works on ANY site, no auth, approximate) and, when GSC is connected, the AUTHORITATIVE GSC URL Inspection status plus the REASON a page is not indexed (crawled-not-indexed, blocked by robots, noindex, canonical, etc.). No LLM, no quota. Returns {coverage_pct, indexed_count, not_indexed, orphans, pages, gsc_used, ...}. Great for spotting pages that exist but Google never indexed."},{"name":"gridar_indexnow_setup","description":"Get the site's IndexNow key + the ownership file to host, so the site can submit URLs to Bing / Yandex / Seznam / Naver (NOT Google - Google does not support IndexNow). Returns {key, key_file_url, key_file_content, instructions}: host a text file containing the key at key_file_url (the domain root) once, then gridar_indexnow_submit works. Fast Bing indexing feeds ChatGPT Search / Copilot / DuckDuckGo (AI visibility). No LLM, no quota."},{"name":"gridar_indexnow_submit","description":"Submit URLs to IndexNow (Bing / Yandex / Seznam / Naver, NOT Google) so they get crawled/indexed fast - useful for AI-search visibility. Pass `urls` (all must be on the site's own host), or omit to submit the site's sitemap URLs. Requires the ownership key file from gridar_indexnow_setup to be hosted first (else status 403). Returns {ok, status, reason, submitted, host}. No LLM, no quota."},{"name":"gridar_submit_sitemap","description":"Re-submit the site's sitemap to GOOGLE Search Console, which makes Google re-read it instead of waiting for its next pass. Use it right after a sitemap changes (new URLs, host moved to/from www) or when gridar_index_coverage shows many pages 'unknown to Google'. Pass `sitemap_url` or omit for <public host>/sitemap.xml; the sitemap must live inside a Search Console property this account owns. Needs GSC connected with write access. Error codes: `gsc_scope_readonly` (HTTP 409, site connected before the scope was widened -> reconnect Search Console), `gsc_reauth_required` (HTTP 401, token expired or revoked -> reconnect), `gsc_not_owner`, `sitemap_hors_propriete`. Re-submitting does NOT force indexing (Google has no API for that); it only refreshes Google's copy of the sitemap, so calling it repeatedly changes nothing and burns Search Console quota. Returns {ok, sitemap, property, needs_reconnect, code, error}. No LLM."}]}