{
    "serverInfo": {
        "name": "SiteGuru",
        "title": "SiteGuru SEO",
        "version": "1.0.0"
    },
    "protocolVersion": "2025-11-25",
    "description": "Access your SiteGuru SEO data: site audits, Google Search Console performance, keywords, competitor analyses and Shopify insights.",
    "documentationUrl": "https://mcp.siteguru.co",
    "transport": {
        "type": "streamable-http",
        "endpoint": "https://mcp.siteguru.co/mcp"
    },
    "capabilities": {
        "tools": {
            "listChanged": false
        }
    },
    "authentication": {
        "type": "oauth2",
        "authorizationServer": "https://mcp.siteguru.co",
        "note": "The MCP endpoint requires an OAuth access token or a SiteGuru access key. Tool discovery is public; every tool call is authenticated."
    },
    "tools": [
        {
            "name": "list_sites",
            "title": "List Sites",
            "description": "List the SiteGuru sites you can access, by domain. Call this first to discover which sites are available; the other tools reference a site by its domain. Each site includes a health_score (the passing percentage of its SEO checks, same as the SiteGuru dashboard — higher is healthier), so you can compare sites and answer \"which site needs the most attention\" (lowest score) without another call; a site with no completed audit yet has health_score null and audit_status \"pending\". Each site also includes available_ranges (the cached Search Console periods, with concrete dates) and default_range — pass one of those range values to the performance tools for an instant cache hit. Each site also has a data_sources object showing which sources are connected — search_console and analytics (Google Analytics 4) — each with `connected` and, when connected, a headline number for the default period (search_console.clicks, analytics.sessions). About half of sites are Search Console only (analytics.connected false), so check this before promising traffic or revenue figures, and don't call the analytics-backed parts for sites without it. Each site also has audit_state (crawl_running, sitecheck_running, link_check_running or idle) and last_audit_completed_at (null when no audit has completed yet). After start_audit, call list_sites again to follow the audit until audit_state is idle.",
            "inputSchema": {
                "properties": {
                    "context": {
                        "description": "Briefly, in your own words, why you are calling this tool — the question or task the user is trying to answer. Helps SiteGuru understand usage and improve.",
                        "type": "string"
                    }
                },
                "type": "object"
            },
            "annotations": {
                "readOnlyHint": true,
                "destructiveHint": false,
                "openWorldHint": false
            },
            "outputSchema": {
                "type": "object",
                "description": "This tool's structured data. The exact keys depend on the tool; the same data is also returned as a human-readable summary in the text content.",
                "additionalProperties": true
            }
        },
        {
            "name": "get_todo_list",
            "title": "Get Todo List",
            "description": "Get the prioritized SEO to-do list for a site — the most important issues to fix, grouped into technical, content and opportunity categories. Pass the site's domain (from list_sites). Each item has a severity and a report_url; include the report_url as a link so the user can open the full report in SiteGuru.",
            "inputSchema": {
                "properties": {
                    "site": {
                        "description": "The site domain — with or without www or https:// (matching ignores them). Use a value from list_sites, e.g. \"example.com\".",
                        "type": "string"
                    },
                    "context": {
                        "description": "Briefly, in your own words, why you are calling this tool — the question or task the user is trying to answer. Helps SiteGuru understand usage and improve.",
                        "type": "string"
                    }
                },
                "type": "object",
                "required": [
                    "site"
                ]
            },
            "annotations": {
                "readOnlyHint": true,
                "destructiveHint": false,
                "openWorldHint": false
            },
            "outputSchema": {
                "type": "object",
                "description": "This tool's structured data. The exact keys depend on the tool; the same data is also returned as a human-readable summary in the text content.",
                "additionalProperties": true
            }
        },
        {
            "name": "get_seo_report",
            "title": "Get Seo Report",
            "description": "Get an SEO report for a site. Reports: indexation, page titles, meta descriptions, headings, image alt texts, structured data, canonical urls, page speed, og tags, orphan pages, hreflangs, broken links, redirected urls, error pages, sitemap, lorem ipsum, backlinks, site-wide checks. Common aliases work (e.g. \"h1\", \"alt tags\", \"title\", \"schema\", \"og\", \"orphans\", \"hreflang\", \"sitemaps\", \"lorem\", \"dummy content\", \"redirects\", \"inbound links\"). Pass the site domain (from list_sites), the report name, and a \"mode\": \"summary\" (default) returns counts + severity; \"issues\" returns per-page rows for the problem pages only — use for \"fix my X\", \"what's broken\"; \"all\" returns per-page rows for EVERY page with its status — use whenever the user asks for \"all my X\", \"every X\", \"list all my X\", \"show me all\", or any audit/inventory intent. If the user says \"all X\" they almost always want mode=\"all\" — do not silently filter to issues. Most page-level reports support all three modes. **site-wide checks** is summary-only — it has no per-page rows. **broken links** and **redirected urls** only check internal links (links between pages on the same site); external links are not validated. **error pages** lists pages that themselves return a 4xx/5xx (or failed to load) — distinct from broken links (internal links pointing at a 404); summary + issues only. **broken links** in issues mode returns one item per broken link (url, response, anchor_text, linking_page_count, up to 3 linking_pages); pass link_url to get every page linking to one of them. **redirected urls** works the same way, with redirects_to and redirect_type on each item. **sitemap** supports summary + issues only; its payload is structured (sitemap files list, plus pages_missing_from_sitemap, redirected_urls_in_sitemap, and error_urls_in_sitemap arrays in issues mode) — not the usual `items` shape. **backlinks** is only available on plans that include DataForSEO backlink data; data refreshes weekly (anything > 6 weeks old is reported as missing with a refresh prompt). The **indexation** report's per-page rows include each page's indexability status (indexable / noindex meta tag / noindex header / blocked by robots.txt) and, when GSC is connected, Google's coverage state. Each summary response advertises its `available_modes`. mode=issues/all results are paginated: read total + has_more and call again with the returned next_offset, or narrow a large site with path_contains (e.g. \"/blog/\").",
            "inputSchema": {
                "properties": {
                    "site": {
                        "description": "The site domain — with or without www or https:// (matching ignores them). Use a value from list_sites, e.g. \"example.com\".",
                        "type": "string"
                    },
                    "report": {
                        "description": "Which report to fetch — one of: \"indexation\", \"page titles\", \"meta descriptions\", \"headings\", \"image alt texts\", \"structured data\", \"canonical urls\", \"page speed\", \"og tags\", \"orphan pages\", \"hreflangs\", \"broken links\", \"redirected urls\", \"error pages\", \"sitemap\", \"lorem ipsum\", \"backlinks\", \"site-wide checks\". Aliases like \"h1\", \"alt tags\", \"title\", \"schema\", \"og\", \"orphans\", \"hreflang\", \"sitemaps\", \"lorem\", \"dummy content\", \"redirects\", \"inbound links\", \"4xx\" are accepted. Note: \"error pages\" lists pages that THEMSELVES return a 4xx/5xx (or fail to load) — different from \"broken links\" (internal links pointing AT a 404) and from the \"sitemap\" report's error URLs (only entries listed in the sitemap).",
                        "type": "string"
                    },
                    "mode": {
                        "description": "What to return — one of \"summary\", \"issues\", \"all\". \"summary\" (default): counts + severity, no pages. Perfect for \"how many\", \"is anything wrong\", a quick overview. \"issues\": per-page rows for ONLY the problem pages, each with url, page title, per-page status and the specifics. Use for \"fix my X\", \"what's broken\", \"what's wrong with my Y\", \"show me the bad ones\". \"all\": per-page rows for EVERY page with its status (passing pages included). Use whenever the user wants the complete inventory — \"all my titles\", \"every page\", \"list all my meta descriptions\", \"show me all\", \"every X regardless of issues\", any audit/inventory intent. If the user says \"all X\" they almost always want mode=\"all\", not \"issues\". Each report's summary response advertises which modes it supports in `available_modes`.",
                        "type": "string"
                    },
                    "limit": {
                        "description": "mode=issues/all only: max pages to return (default 100, max 200).",
                        "type": "integer"
                    },
                    "offset": {
                        "description": "mode=issues/all only: number of pages to skip, for pagination. Use the next_offset from the previous response.",
                        "type": "integer"
                    },
                    "path_contains": {
                        "description": "mode=issues/all only: return only pages whose URL contains this substring (e.g. \"/blog/\"). Useful to focus on a section of a large site. For broken links and redirected urls, it filters on the linking page.",
                        "type": "string"
                    },
                    "link_url": {
                        "description": "broken links and redirected urls only: a link url from a mode=\"issues\" response. Returns every page linking to it, paginated with limit/offset.",
                        "type": "string"
                    },
                    "context": {
                        "description": "Briefly, in your own words, why you are calling this tool — the question or task the user is trying to answer. Helps SiteGuru understand usage and improve.",
                        "type": "string"
                    }
                },
                "type": "object",
                "required": [
                    "site",
                    "report"
                ]
            },
            "annotations": {
                "readOnlyHint": true,
                "destructiveHint": false,
                "openWorldHint": false
            },
            "outputSchema": {
                "type": "object",
                "description": "This tool's structured data. The exact keys depend on the tool; the same data is also returned as a human-readable summary in the text content.",
                "additionalProperties": true
            }
        },
        {
            "name": "get_page_report",
            "title": "Get Page Report",
            "description": "Get the full SEO audit for a single page on a site. Returns the page's actual content (title, first H1, meta description, canonical URL, page speed score), its indexation status (including Google's coverage state when GSC is connected), and the list of issues (warnings + suggestions) and compliments from every check the audit ran. Use this when the user asks about one specific page — e.g. \"what's wrong with my homepage\", \"audit /about\", \"list everything on this page\". For check-by-check rollups across many pages, use get_seo_report instead. Modes: \"summary\" (default) returns the page block + counts only; \"issues\" adds the warnings + suggestions list; \"all\" also adds the compliments.",
            "inputSchema": {
                "properties": {
                    "site": {
                        "description": "The site domain — with or without www or https:// (matching ignores them). Use a value from list_sites, e.g. \"example.com\".",
                        "type": "string"
                    },
                    "page": {
                        "description": "The page on the site to audit. Accepts a full URL (\"https://example.com/about\"), an absolute path (\"/about\"), or just a path segment (\"about\"). Trailing slashes are ignored. Use \"homepage\" or \"/\" or the bare site URL for the home page.",
                        "type": "string"
                    },
                    "mode": {
                        "description": "What to return — one of \"summary\", \"issues\", \"all\". \"summary\" (default): the page block + warning/suggestion/compliment counts, no per-check details. \"issues\": adds an `issues` array with every warning and suggestion check (title, description, severity). \"all\": adds both the `issues` array AND a `compliments` array, so the agent can also tell the user what's working.",
                        "type": "string"
                    },
                    "context": {
                        "description": "Briefly, in your own words, why you are calling this tool — the question or task the user is trying to answer. Helps SiteGuru understand usage and improve.",
                        "type": "string"
                    }
                },
                "type": "object",
                "required": [
                    "site",
                    "page"
                ]
            },
            "annotations": {
                "readOnlyHint": true,
                "destructiveHint": false,
                "openWorldHint": false
            },
            "outputSchema": {
                "type": "object",
                "description": "This tool's structured data. The exact keys depend on the tool; the same data is also returned as a human-readable summary in the text content.",
                "additionalProperties": true
            }
        },
        {
            "name": "find_pages",
            "title": "Find Pages",
            "description": "List and search a site's pages by SEO health — to find a specific page or surface the worst-scoring ones. Returns each page's path, title, type and a health_score (the passing percentage of its on-page checks, same as the SiteGuru dashboard — higher is healthier), plus the previous score for a trend. Worst-first by default, so \"find my worst pages\" is one call. Use search to find a specific page by path or title (e.g. \"pricing\") or to scope to a whole section (e.g. \"/seo-academy\"), sort to order by \"worst\" or \"best\", and limit to cap results. Results are paginated: the response carries a `pagination` block with the total match count and a `next_offset` — to read a whole section, keep calling with offset = next_offset until has_more is false. Pass the site domain from list_sites. Follow up on any page with get_page_report.",
            "inputSchema": {
                "properties": {
                    "site": {
                        "description": "The site domain (from list_sites), e.g. \"example.com\". With or without www/https:// — matching ignores them.",
                        "type": "string"
                    },
                    "search": {
                        "description": "Optional. Return only pages whose path or title contains this text — use it to locate a specific page like \"pricing\" or \"/about\".",
                        "type": "string"
                    },
                    "sort": {
                        "description": "Order of results: \"worst\" (default) lists the pages with the most issues first; \"best\" lists the healthiest pages first.",
                        "type": "string"
                    },
                    "limit": {
                        "description": "Optional. Page size — maximum number of pages to return in one call (default 75, max 200).",
                        "type": "integer"
                    },
                    "offset": {
                        "description": "Optional. Number of pages to skip before returning results (default 0). To page through a section, pass the `next_offset` from the previous response's `pagination` block; stop when has_more is false.",
                        "type": "integer"
                    },
                    "context": {
                        "description": "Briefly, in your own words, why you are calling this tool — the question or task the user is trying to answer. Helps SiteGuru understand usage and improve.",
                        "type": "string"
                    }
                },
                "type": "object",
                "required": [
                    "site"
                ]
            },
            "annotations": {
                "readOnlyHint": true,
                "destructiveHint": false,
                "openWorldHint": false
            },
            "outputSchema": {
                "type": "object",
                "description": "This tool's structured data. The exact keys depend on the tool; the same data is also returned as a human-readable summary in the text content.",
                "additionalProperties": true
            }
        },
        {
            "name": "get_traffic_overview",
            "title": "Get Traffic Overview",
            "description": "Get a traffic overview for a site and date range from its two data sources, plus its top pages and top search queries. The payload has a `search_console` block (Google Search Console: clicks, impressions, CTR) and an `analytics` block (Google Analytics 4: sessions, users, and revenue for ecommerce sites) — each metric carries the change vs the comparison period, and each block has its own `status`. The `analytics` status is \"ok\" with the metrics, \"not_connected\" if the site has no Google Analytics 4 connection (about half of sites are Search Console only), or \"not_fetched\" if GA data hasn't been pulled for this date range yet — report the Search Console figures regardless. Pass the site domain (from list_sites); omit the dates to use the user's default period. Each top page includes its path so you can follow up with get_page_report.",
            "inputSchema": {
                "properties": {
                    "site": {
                        "description": "The site domain — with or without www or https:// (matching ignores them). Use a value from list_sites, e.g. \"example.com\".",
                        "type": "string"
                    },
                    "range": {
                        "description": "PREFERRED. A named period guaranteed to be cached — typically \"last_7_days\", \"last_30_days\" or \"last_90_days\" (use the exact `range` values listed in this site's available_ranges, from list_sites or a previous response). This always resolves to an instant cache hit, so use it instead of computing start/end yourself whenever the user wants a standard recent window. If you pass range, omit start and end.",
                        "type": "string"
                    },
                    "start": {
                        "description": "Start of a CUSTOM period, as YYYY-MM-DD. Only use start/end for a window not offered in available_ranges; otherwise prefer range. Do not invent dates for \"last 30 days\" — use range, or copy the exact start/end from an available_ranges entry. Omit start, end and range to use the user's default period (also a guaranteed hit).",
                        "type": "string"
                    },
                    "end": {
                        "description": "End of a custom period, as YYYY-MM-DD. Search Console lags a few days, so the most recent usable end is what available_ranges reports — don't assume today. Required whenever start is given.",
                        "type": "string"
                    },
                    "compare_start": {
                        "description": "Optional comparison-period start (YYYY-MM-DD), e.g. for year-over-year. If omitted, the immediately preceding period of the same length is used. Provide both compare_start and compare_end, or neither.",
                        "type": "string"
                    },
                    "compare_end": {
                        "description": "Optional comparison-period end (YYYY-MM-DD). Provide both compare_start and compare_end, or neither.",
                        "type": "string"
                    },
                    "context": {
                        "description": "Briefly, in your own words, why you are calling this tool — the question or task the user is trying to answer. Helps SiteGuru understand usage and improve.",
                        "type": "string"
                    }
                },
                "type": "object",
                "required": [
                    "site"
                ]
            },
            "annotations": {
                "readOnlyHint": true,
                "destructiveHint": false,
                "openWorldHint": false
            },
            "outputSchema": {
                "type": "object",
                "description": "This tool's structured data. The exact keys depend on the tool; the same data is also returned as a human-readable summary in the text content.",
                "additionalProperties": true
            }
        },
        {
            "name": "get_traffic_sources",
            "title": "Get Traffic Sources",
            "description": "Get the site's main traffic sources from Google Analytics 4 for a date range — the channels (e.g. Organic Search, Direct, Referral, Paid Search, Organic Social) sending sessions, largest first. Each source has its sessions and a share_pct (its percentage of total sessions), plus revenue for ecommerce sites; sessions and revenue each carry value, previous and the change vs the comparison period, and the response includes total_sessions. This is Google Analytics data, so analytics_status is \"ok\" with the sources, \"not_connected\" if the site has no Google Analytics 4 connection (about half of sites are Search Console only), or \"not_fetched\" if GA data has not been pulled for this date range yet. Pass the site domain (from list_sites); omit the dates to use the user's default period.",
            "inputSchema": {
                "properties": {
                    "site": {
                        "description": "The site domain — with or without www or https:// (matching ignores them). Use a value from list_sites, e.g. \"example.com\".",
                        "type": "string"
                    },
                    "range": {
                        "description": "PREFERRED. A named period guaranteed to be cached — typically \"last_7_days\", \"last_30_days\" or \"last_90_days\" (use the exact `range` values listed in this site's available_ranges, from list_sites or a previous response). This always resolves to an instant cache hit, so use it instead of computing start/end yourself whenever the user wants a standard recent window. If you pass range, omit start and end.",
                        "type": "string"
                    },
                    "start": {
                        "description": "Start of a CUSTOM period, as YYYY-MM-DD. Only use start/end for a window not offered in available_ranges; otherwise prefer range. Do not invent dates for \"last 30 days\" — use range, or copy the exact start/end from an available_ranges entry. Omit start, end and range to use the user's default period (also a guaranteed hit).",
                        "type": "string"
                    },
                    "end": {
                        "description": "End of a custom period, as YYYY-MM-DD. Search Console lags a few days, so the most recent usable end is what available_ranges reports — don't assume today. Required whenever start is given.",
                        "type": "string"
                    },
                    "compare_start": {
                        "description": "Optional comparison-period start (YYYY-MM-DD), e.g. for year-over-year. If omitted, the immediately preceding period of the same length is used. Provide both compare_start and compare_end, or neither.",
                        "type": "string"
                    },
                    "compare_end": {
                        "description": "Optional comparison-period end (YYYY-MM-DD). Provide both compare_start and compare_end, or neither.",
                        "type": "string"
                    },
                    "context": {
                        "description": "Briefly, in your own words, why you are calling this tool — the question or task the user is trying to answer. Helps SiteGuru understand usage and improve.",
                        "type": "string"
                    }
                },
                "type": "object",
                "required": [
                    "site"
                ]
            },
            "annotations": {
                "readOnlyHint": true,
                "destructiveHint": false,
                "openWorldHint": false
            },
            "outputSchema": {
                "type": "object",
                "description": "This tool's structured data. The exact keys depend on the tool; the same data is also returned as a human-readable summary in the text content.",
                "additionalProperties": true
            }
        },
        {
            "name": "get_top_keywords",
            "title": "Get Top Keywords",
            "description": "Get the site's top 100 Google Search Console keywords for a date range — clicks, impressions, CTR and average position, each with the change vs the comparison period. Pass the site domain (from list_sites); omit the dates to use the user's default period. Note on average position: `avg_position.change` is the absolute change in Google ranking (current minus previous), NOT a percentage. Position 1 is the top of Google, so a NEGATIVE change means the keyword moved UP and improved, and a positive change means it dropped — describe it that way to the user.",
            "inputSchema": {
                "properties": {
                    "site": {
                        "description": "The site domain — with or without www or https:// (matching ignores them). Use a value from list_sites, e.g. \"example.com\".",
                        "type": "string"
                    },
                    "range": {
                        "description": "PREFERRED. A named period guaranteed to be cached — typically \"last_7_days\", \"last_30_days\" or \"last_90_days\" (use the exact `range` values listed in this site's available_ranges, from list_sites or a previous response). This always resolves to an instant cache hit, so use it instead of computing start/end yourself whenever the user wants a standard recent window. If you pass range, omit start and end.",
                        "type": "string"
                    },
                    "start": {
                        "description": "Start of a CUSTOM period, as YYYY-MM-DD. Only use start/end for a window not offered in available_ranges; otherwise prefer range. Do not invent dates for \"last 30 days\" — use range, or copy the exact start/end from an available_ranges entry. Omit start, end and range to use the user's default period (also a guaranteed hit).",
                        "type": "string"
                    },
                    "end": {
                        "description": "End of a custom period, as YYYY-MM-DD. Search Console lags a few days, so the most recent usable end is what available_ranges reports — don't assume today. Required whenever start is given.",
                        "type": "string"
                    },
                    "compare_start": {
                        "description": "Optional comparison-period start (YYYY-MM-DD), e.g. for year-over-year. If omitted, the immediately preceding period of the same length is used. Provide both compare_start and compare_end, or neither.",
                        "type": "string"
                    },
                    "compare_end": {
                        "description": "Optional comparison-period end (YYYY-MM-DD). Provide both compare_start and compare_end, or neither.",
                        "type": "string"
                    },
                    "context": {
                        "description": "Briefly, in your own words, why you are calling this tool — the question or task the user is trying to answer. Helps SiteGuru understand usage and improve.",
                        "type": "string"
                    }
                },
                "type": "object",
                "required": [
                    "site"
                ]
            },
            "annotations": {
                "readOnlyHint": true,
                "destructiveHint": false,
                "openWorldHint": false
            },
            "outputSchema": {
                "type": "object",
                "description": "This tool's structured data. The exact keys depend on the tool; the same data is also returned as a human-readable summary in the text content.",
                "additionalProperties": true
            }
        },
        {
            "name": "get_top_pages",
            "title": "Get Top Pages",
            "description": "Get the site's top pages by Google Search Console clicks for a date range — each with clicks, impressions and average position (each with the change vs the comparison period), plus the single keyword that page wins the most clicks on (its top_keyword, with full metrics). When the site has Google Analytics 4 connected, each page also includes pageviews (GA4, with the change vs the comparison period); GSC-only sites omit it. Returns the top 50 by default; results are paginated, so the response carries a `pagination` block with the total page count and a `next_offset` — to walk deeper than the first page, keep calling with offset = next_offset until has_more is false (use limit up to 200 to fetch more per call). Use it to see which pages drive the most search traffic and what query each one ranks for; follow up on any page with get_page_report. Pass the site domain (from list_sites); omit the dates to use the user's default period. For average position, `avg_position.change` is the absolute move in Google ranking (current minus previous) — a NEGATIVE change means the page or keyword moved UP and improved, not a percentage.",
            "inputSchema": {
                "properties": {
                    "site": {
                        "description": "The site domain — with or without www or https:// (matching ignores them). Use a value from list_sites, e.g. \"example.com\".",
                        "type": "string"
                    },
                    "range": {
                        "description": "PREFERRED. A named period guaranteed to be cached — typically \"last_7_days\", \"last_30_days\" or \"last_90_days\" (use the exact `range` values listed in this site's available_ranges, from list_sites or a previous response). This always resolves to an instant cache hit, so use it instead of computing start/end yourself whenever the user wants a standard recent window. If you pass range, omit start and end.",
                        "type": "string"
                    },
                    "start": {
                        "description": "Start of a CUSTOM period, as YYYY-MM-DD. Only use start/end for a window not offered in available_ranges; otherwise prefer range. Do not invent dates for \"last 30 days\" — use range, or copy the exact start/end from an available_ranges entry. Omit start, end and range to use the user's default period (also a guaranteed hit).",
                        "type": "string"
                    },
                    "end": {
                        "description": "End of a custom period, as YYYY-MM-DD. Search Console lags a few days, so the most recent usable end is what available_ranges reports — don't assume today. Required whenever start is given.",
                        "type": "string"
                    },
                    "compare_start": {
                        "description": "Optional comparison-period start (YYYY-MM-DD), e.g. for year-over-year. If omitted, the immediately preceding period of the same length is used. Provide both compare_start and compare_end, or neither.",
                        "type": "string"
                    },
                    "compare_end": {
                        "description": "Optional comparison-period end (YYYY-MM-DD). Provide both compare_start and compare_end, or neither.",
                        "type": "string"
                    },
                    "limit": {
                        "description": "Optional. Page size — number of pages to return in one call (default 50, max 200).",
                        "type": "integer"
                    },
                    "offset": {
                        "description": "Optional. Number of pages to skip before returning results (default 0). To page deeper, pass the `next_offset` from the previous response's `pagination` block; stop when has_more is false.",
                        "type": "integer"
                    },
                    "context": {
                        "description": "Briefly, in your own words, why you are calling this tool — the question or task the user is trying to answer. Helps SiteGuru understand usage and improve.",
                        "type": "string"
                    }
                },
                "type": "object",
                "required": [
                    "site"
                ]
            },
            "annotations": {
                "readOnlyHint": true,
                "destructiveHint": false,
                "openWorldHint": false
            },
            "outputSchema": {
                "type": "object",
                "description": "This tool's structured data. The exact keys depend on the tool; the same data is also returned as a human-readable summary in the text content.",
                "additionalProperties": true
            }
        },
        {
            "name": "get_focus_keywords",
            "title": "Get Focus Keywords",
            "description": "Get the site's focus keywords with how they are performing for a date range — the keywords the user has singled out as the important ones they want to rank for, and that SiteGuru gives them a to-do list for. Each focus keyword has the page that should rank plus its clicks, impressions and average Google position (each with the change vs the comparison period). For average position, `avg_position.change` is the absolute move in ranking (current minus previous) — a NEGATIVE change means it moved UP and improved, not a percentage. Pass the site domain (from list_sites); omit the dates to use the user's default period. Set include_actions=true when the user asks how to improve or what to do for a focus keyword: it adds optimization_actions (a short title + status per on-page area) for each one. For the user's full keyword tracking list, use get_tracked_keywords. Not every site has focus keywords: when the status is \"not_available\", do not suggest setting them up; use get_tracked_keywords or get_top_keywords instead.",
            "inputSchema": {
                "properties": {
                    "site": {
                        "description": "The site domain — with or without www or https:// (matching ignores them). Use a value from list_sites, e.g. \"example.com\".",
                        "type": "string"
                    },
                    "range": {
                        "description": "PREFERRED. A named period guaranteed to be cached — typically \"last_7_days\", \"last_30_days\" or \"last_90_days\" (use the exact `range` values listed in this site's available_ranges, from list_sites or a previous response). This always resolves to an instant cache hit, so use it instead of computing start/end yourself whenever the user wants a standard recent window. If you pass range, omit start and end.",
                        "type": "string"
                    },
                    "start": {
                        "description": "Start of a CUSTOM period, as YYYY-MM-DD. Only use start/end for a window not offered in available_ranges; otherwise prefer range. Do not invent dates for \"last 30 days\" — use range, or copy the exact start/end from an available_ranges entry. Omit start, end and range to use the user's default period (also a guaranteed hit).",
                        "type": "string"
                    },
                    "end": {
                        "description": "End of a custom period, as YYYY-MM-DD. Search Console lags a few days, so the most recent usable end is what available_ranges reports — don't assume today. Required whenever start is given.",
                        "type": "string"
                    },
                    "compare_start": {
                        "description": "Optional comparison-period start (YYYY-MM-DD), e.g. for year-over-year. If omitted, the immediately preceding period of the same length is used. Provide both compare_start and compare_end, or neither.",
                        "type": "string"
                    },
                    "compare_end": {
                        "description": "Optional comparison-period end (YYYY-MM-DD). Provide both compare_start and compare_end, or neither.",
                        "type": "string"
                    },
                    "include_actions": {
                        "description": "Optional, default false. When true, add optimization_actions — a distilled page-optimization checklist (a short action title plus status per on-page area: title, headings, meta description, links, page health, word count) for each focus keyword. Use it for \"how do I improve my focus keywords / what should I do\"; leave it off for a plain performance read.",
                        "type": "boolean"
                    },
                    "context": {
                        "description": "Briefly, in your own words, why you are calling this tool — the question or task the user is trying to answer. Helps SiteGuru understand usage and improve.",
                        "type": "string"
                    }
                },
                "type": "object",
                "required": [
                    "site"
                ]
            },
            "annotations": {
                "readOnlyHint": true,
                "destructiveHint": false,
                "openWorldHint": false
            },
            "outputSchema": {
                "type": "object",
                "description": "This tool's structured data. The exact keys depend on the tool; the same data is also returned as a human-readable summary in the text content.",
                "additionalProperties": true
            }
        },
        {
            "name": "get_tracked_keywords",
            "title": "Get Tracked Keywords",
            "description": "Get the site's tracked keywords — every keyword the user follows in SiteGuru — with how each performed for a date range: clicks, impressions, CTR and average Google position, each with the change vs the comparison period. Use it for \"my tracked keywords\", \"how are the keywords I track doing\", \"my keyword rankings\". A keyword the user has also singled out as a focus keyword is flagged is_focus_keyword; get_focus_keywords covers those in more depth, with the page that should rank and a to-do list for getting there. A tracked keyword the site has no Search Console data for in this period is still listed, with has_search_data false and null metrics — say it has no search data, never that it got 0 clicks or ranks at position 0. Returns the top 100 by clicks by default; the response carries a `pagination` block, so to walk further keep calling with offset = next_offset until has_more is false. Pass the site domain (from list_sites); omit the dates to use the user's default period. For average position, `avg_position.change` is the absolute move in ranking (current minus previous) — a NEGATIVE change means the keyword moved UP and improved, not a percentage.",
            "inputSchema": {
                "properties": {
                    "site": {
                        "description": "The site domain — with or without www or https:// (matching ignores them). Use a value from list_sites, e.g. \"example.com\".",
                        "type": "string"
                    },
                    "range": {
                        "description": "PREFERRED. A named period guaranteed to be cached — typically \"last_7_days\", \"last_30_days\" or \"last_90_days\" (use the exact `range` values listed in this site's available_ranges, from list_sites or a previous response). This always resolves to an instant cache hit, so use it instead of computing start/end yourself whenever the user wants a standard recent window. If you pass range, omit start and end.",
                        "type": "string"
                    },
                    "start": {
                        "description": "Start of a CUSTOM period, as YYYY-MM-DD. Only use start/end for a window not offered in available_ranges; otherwise prefer range. Do not invent dates for \"last 30 days\" — use range, or copy the exact start/end from an available_ranges entry. Omit start, end and range to use the user's default period (also a guaranteed hit).",
                        "type": "string"
                    },
                    "end": {
                        "description": "End of a custom period, as YYYY-MM-DD. Search Console lags a few days, so the most recent usable end is what available_ranges reports — don't assume today. Required whenever start is given.",
                        "type": "string"
                    },
                    "compare_start": {
                        "description": "Optional comparison-period start (YYYY-MM-DD), e.g. for year-over-year. If omitted, the immediately preceding period of the same length is used. Provide both compare_start and compare_end, or neither.",
                        "type": "string"
                    },
                    "compare_end": {
                        "description": "Optional comparison-period end (YYYY-MM-DD). Provide both compare_start and compare_end, or neither.",
                        "type": "string"
                    },
                    "limit": {
                        "description": "Optional. Page size — number of tracked keywords to return in one call (default 100, max 200).",
                        "type": "integer"
                    },
                    "offset": {
                        "description": "Optional. Number of tracked keywords to skip before returning results (default 0). To page deeper, pass the `next_offset` from the previous response's `pagination` block; stop when has_more is false.",
                        "type": "integer"
                    },
                    "context": {
                        "description": "Briefly, in your own words, why you are calling this tool — the question or task the user is trying to answer. Helps SiteGuru understand usage and improve.",
                        "type": "string"
                    }
                },
                "type": "object",
                "required": [
                    "site"
                ]
            },
            "annotations": {
                "readOnlyHint": true,
                "destructiveHint": false,
                "openWorldHint": false
            },
            "outputSchema": {
                "type": "object",
                "description": "This tool's structured data. The exact keys depend on the tool; the same data is also returned as a human-readable summary in the text content.",
                "additionalProperties": true
            }
        },
        {
            "name": "get_search_topics",
            "title": "Get Search Topics",
            "description": "Get the site's Search Topics (also known as keyword clusters) — each Search Topic groups related keywords the site ranks for. Called WITHOUT a topic, it lists every Search Topic with its combined clicks, impressions, CTR and average Google position (each with the change vs the comparison period), its keyword_count and search volume, and a preview of its top keywords. Pass topic=\"<name>\" (a name from the list) to drill into ONE Search Topic and get its full keyword list, each keyword with the same metrics plus its search volume. For average position, `avg_position.change` is the absolute move in ranking (current minus previous, NEGATIVE = moved up and improved); a position is null when the topic or keyword is not ranking. Search volume is shown only when already known in SiteGuru — it is never fetched on demand, and search_volume.complete tells you whether every keyword had a known volume. Pass the site domain (from list_sites); omit the dates to use the user's default period.",
            "inputSchema": {
                "properties": {
                    "site": {
                        "description": "The site domain — with or without www or https:// (matching ignores them). Use a value from list_sites, e.g. \"example.com\".",
                        "type": "string"
                    },
                    "range": {
                        "description": "PREFERRED. A named period guaranteed to be cached — typically \"last_7_days\", \"last_30_days\" or \"last_90_days\" (use the exact `range` values listed in this site's available_ranges, from list_sites or a previous response). This always resolves to an instant cache hit, so use it instead of computing start/end yourself whenever the user wants a standard recent window. If you pass range, omit start and end.",
                        "type": "string"
                    },
                    "start": {
                        "description": "Start of a CUSTOM period, as YYYY-MM-DD. Only use start/end for a window not offered in available_ranges; otherwise prefer range. Do not invent dates for \"last 30 days\" — use range, or copy the exact start/end from an available_ranges entry. Omit start, end and range to use the user's default period (also a guaranteed hit).",
                        "type": "string"
                    },
                    "end": {
                        "description": "End of a custom period, as YYYY-MM-DD. Search Console lags a few days, so the most recent usable end is what available_ranges reports — don't assume today. Required whenever start is given.",
                        "type": "string"
                    },
                    "compare_start": {
                        "description": "Optional comparison-period start (YYYY-MM-DD), e.g. for year-over-year. If omitted, the immediately preceding period of the same length is used. Provide both compare_start and compare_end, or neither.",
                        "type": "string"
                    },
                    "compare_end": {
                        "description": "Optional comparison-period end (YYYY-MM-DD). Provide both compare_start and compare_end, or neither.",
                        "type": "string"
                    },
                    "topic": {
                        "description": "Optional. The exact name of a Search Topic (from a previous list call) to drill into — returns that one topic with its full keyword list. Omit to list all Search Topics with their combined KPIs and a top-keyword preview.",
                        "type": "string"
                    },
                    "context": {
                        "description": "Briefly, in your own words, why you are calling this tool — the question or task the user is trying to answer. Helps SiteGuru understand usage and improve.",
                        "type": "string"
                    }
                },
                "type": "object",
                "required": [
                    "site"
                ]
            },
            "annotations": {
                "readOnlyHint": true,
                "destructiveHint": false,
                "openWorldHint": false
            },
            "outputSchema": {
                "type": "object",
                "description": "This tool's structured data. The exact keys depend on the tool; the same data is also returned as a human-readable summary in the text content.",
                "additionalProperties": true
            }
        },
        {
            "name": "get_keyword_cannibalization",
            "title": "Get Keyword Cannibalization",
            "description": "Find keyword cannibalization for a site and date range — search queries where two or more of the site's own pages compete for the same keyword (also called competing pages). This can split clicks between pages and hold rankings back. Each result is a keyword with its combined clicks, impressions and average Google position (each with the change vs the comparison period) and a competing_pages list — every competing page with its own clicks, impressions and position. Only keywords with real traffic are included (each page must take a meaningful share of the keyword's clicks), and hreflang / localized alternates are not counted as cannibalization. For average position, `avg_position.change` is the absolute move in ranking (current minus previous, NEGATIVE = moved up), not a percentage. Pass the site domain (from list_sites); omit the dates to use the user's default period.",
            "inputSchema": {
                "properties": {
                    "site": {
                        "description": "The site domain — with or without www or https:// (matching ignores them). Use a value from list_sites, e.g. \"example.com\".",
                        "type": "string"
                    },
                    "range": {
                        "description": "PREFERRED. A named period guaranteed to be cached — typically \"last_7_days\", \"last_30_days\" or \"last_90_days\" (use the exact `range` values listed in this site's available_ranges, from list_sites or a previous response). This always resolves to an instant cache hit, so use it instead of computing start/end yourself whenever the user wants a standard recent window. If you pass range, omit start and end.",
                        "type": "string"
                    },
                    "start": {
                        "description": "Start of a CUSTOM period, as YYYY-MM-DD. Only use start/end for a window not offered in available_ranges; otherwise prefer range. Do not invent dates for \"last 30 days\" — use range, or copy the exact start/end from an available_ranges entry. Omit start, end and range to use the user's default period (also a guaranteed hit).",
                        "type": "string"
                    },
                    "end": {
                        "description": "End of a custom period, as YYYY-MM-DD. Search Console lags a few days, so the most recent usable end is what available_ranges reports — don't assume today. Required whenever start is given.",
                        "type": "string"
                    },
                    "compare_start": {
                        "description": "Optional comparison-period start (YYYY-MM-DD), e.g. for year-over-year. If omitted, the immediately preceding period of the same length is used. Provide both compare_start and compare_end, or neither.",
                        "type": "string"
                    },
                    "compare_end": {
                        "description": "Optional comparison-period end (YYYY-MM-DD). Provide both compare_start and compare_end, or neither.",
                        "type": "string"
                    },
                    "context": {
                        "description": "Briefly, in your own words, why you are calling this tool — the question or task the user is trying to answer. Helps SiteGuru understand usage and improve.",
                        "type": "string"
                    }
                },
                "type": "object",
                "required": [
                    "site"
                ]
            },
            "annotations": {
                "readOnlyHint": true,
                "destructiveHint": false,
                "openWorldHint": false
            },
            "outputSchema": {
                "type": "object",
                "description": "This tool's structured data. The exact keys depend on the tool; the same data is also returned as a human-readable summary in the text content.",
                "additionalProperties": true
            }
        },
        {
            "name": "get_low_hanging_fruit",
            "title": "Get Low Hanging Fruit",
            "description": "Get up to 20 low-hanging-fruit keywords for a site and date range — queries already getting impressions that rank just below the click-winning positions, i.e. the quickest opportunities to improve. Each item includes the page path so you can follow up with get_page_report. Pass the site domain (from list_sites); omit the dates to use the user's default period.",
            "inputSchema": {
                "properties": {
                    "site": {
                        "description": "The site domain — with or without www or https:// (matching ignores them). Use a value from list_sites, e.g. \"example.com\".",
                        "type": "string"
                    },
                    "range": {
                        "description": "PREFERRED. A named period guaranteed to be cached — typically \"last_7_days\", \"last_30_days\" or \"last_90_days\" (use the exact `range` values listed in this site's available_ranges, from list_sites or a previous response). This always resolves to an instant cache hit, so use it instead of computing start/end yourself whenever the user wants a standard recent window. If you pass range, omit start and end.",
                        "type": "string"
                    },
                    "start": {
                        "description": "Start of a CUSTOM period, as YYYY-MM-DD. Only use start/end for a window not offered in available_ranges; otherwise prefer range. Do not invent dates for \"last 30 days\" — use range, or copy the exact start/end from an available_ranges entry. Omit start, end and range to use the user's default period (also a guaranteed hit).",
                        "type": "string"
                    },
                    "end": {
                        "description": "End of a custom period, as YYYY-MM-DD. Search Console lags a few days, so the most recent usable end is what available_ranges reports — don't assume today. Required whenever start is given.",
                        "type": "string"
                    },
                    "compare_start": {
                        "description": "Optional comparison-period start (YYYY-MM-DD), e.g. for year-over-year. If omitted, the immediately preceding period of the same length is used. Provide both compare_start and compare_end, or neither.",
                        "type": "string"
                    },
                    "compare_end": {
                        "description": "Optional comparison-period end (YYYY-MM-DD). Provide both compare_start and compare_end, or neither.",
                        "type": "string"
                    },
                    "context": {
                        "description": "Briefly, in your own words, why you are calling this tool — the question or task the user is trying to answer. Helps SiteGuru understand usage and improve.",
                        "type": "string"
                    }
                },
                "type": "object",
                "required": [
                    "site"
                ]
            },
            "annotations": {
                "readOnlyHint": true,
                "destructiveHint": false,
                "openWorldHint": false
            },
            "outputSchema": {
                "type": "object",
                "description": "This tool's structured data. The exact keys depend on the tool; the same data is also returned as a human-readable summary in the text content.",
                "additionalProperties": true
            }
        },
        {
            "name": "get_top_products",
            "title": "Get Top Products",
            "description": "Get a Shopify store's top products for a date range — products ranked by revenue then Search Console clicks. Each product has its clicks and impressions (Google Search Console) and, when Google Analytics 4 is connected and the product has sales, revenue (with its currency) and organic_revenue (revenue from Organic Search sessions), each carrying the change vs the comparison period, plus organic_share (organic revenue as a percentage of revenue). Only Shopify sites have products: for any other site the status is \"not_shopify\". This is the performance angle — to see what to fix on product pages, use get_product_issues. Pass the site domain (from list_sites); omit the dates to use the user's default period.",
            "inputSchema": {
                "properties": {
                    "site": {
                        "description": "The site domain — with or without www or https:// (matching ignores them). Use a value from list_sites, e.g. \"example.com\".",
                        "type": "string"
                    },
                    "range": {
                        "description": "PREFERRED. A named period guaranteed to be cached — typically \"last_7_days\", \"last_30_days\" or \"last_90_days\" (use the exact `range` values listed in this site's available_ranges, from list_sites or a previous response). This always resolves to an instant cache hit, so use it instead of computing start/end yourself whenever the user wants a standard recent window. If you pass range, omit start and end.",
                        "type": "string"
                    },
                    "start": {
                        "description": "Start of a CUSTOM period, as YYYY-MM-DD. Only use start/end for a window not offered in available_ranges; otherwise prefer range. Do not invent dates for \"last 30 days\" — use range, or copy the exact start/end from an available_ranges entry. Omit start, end and range to use the user's default period (also a guaranteed hit).",
                        "type": "string"
                    },
                    "end": {
                        "description": "End of a custom period, as YYYY-MM-DD. Search Console lags a few days, so the most recent usable end is what available_ranges reports — don't assume today. Required whenever start is given.",
                        "type": "string"
                    },
                    "compare_start": {
                        "description": "Optional comparison-period start (YYYY-MM-DD), e.g. for year-over-year. If omitted, the immediately preceding period of the same length is used. Provide both compare_start and compare_end, or neither.",
                        "type": "string"
                    },
                    "compare_end": {
                        "description": "Optional comparison-period end (YYYY-MM-DD). Provide both compare_start and compare_end, or neither.",
                        "type": "string"
                    },
                    "context": {
                        "description": "Briefly, in your own words, why you are calling this tool — the question or task the user is trying to answer. Helps SiteGuru understand usage and improve.",
                        "type": "string"
                    }
                },
                "type": "object",
                "required": [
                    "site"
                ]
            },
            "annotations": {
                "readOnlyHint": true,
                "destructiveHint": false,
                "openWorldHint": false
            },
            "outputSchema": {
                "type": "object",
                "description": "This tool's structured data. The exact keys depend on the tool; the same data is also returned as a human-readable summary in the text content.",
                "additionalProperties": true
            }
        },
        {
            "name": "get_product_issues",
            "title": "Get Product Issues",
            "description": "Find the Shopify products that need SEO work — products ranked by health score, lowest (most issues) first, each with its failing checks (title, meta description, H1, thin content, internal links and more). Pass a product handle (from the list) to drill into that one product's full SEO checklist. The list is paginated: the response carries a `pagination` block with the total and a `next_offset`; to read every product, keep calling with offset = next_offset until has_more is false. Only Shopify sites have products: for any other site the status is \"not_shopify\". This is the \"what to fix\" angle — to rank products by traffic or sales, use get_top_products. Pass the site domain (from list_sites).",
            "inputSchema": {
                "properties": {
                    "site": {
                        "description": "The site domain (exactly as returned by list_sites).",
                        "type": "string"
                    },
                    "product": {
                        "description": "Optional. A product handle (from the list) to drill into its full SEO checklist. Omit to list the products with the most issues.",
                        "type": "string"
                    },
                    "limit": {
                        "description": "Optional. Page size: maximum number of products to return in one call (default 50, max 200).",
                        "type": "integer"
                    },
                    "offset": {
                        "description": "Optional. Number of products to skip before returning results (default 0). To read the whole list, pass the `next_offset` from the previous response's `pagination` block; stop when has_more is false.",
                        "type": "integer"
                    },
                    "context": {
                        "description": "Briefly, in your own words, why you are calling this tool — the question or task the user is trying to answer. Helps SiteGuru understand usage and improve.",
                        "type": "string"
                    }
                },
                "type": "object",
                "required": [
                    "site"
                ]
            },
            "annotations": {
                "readOnlyHint": true,
                "destructiveHint": false,
                "openWorldHint": false
            },
            "outputSchema": {
                "type": "object",
                "description": "This tool's structured data. The exact keys depend on the tool; the same data is also returned as a human-readable summary in the text content.",
                "additionalProperties": true
            }
        },
        {
            "name": "get_top_collections",
            "title": "Get Top Collections",
            "description": "Get a Shopify store's top collections (category pages) for a date range — collections ranked by revenue then Search Console clicks. Each collection has its clicks and impressions (Google Search Console, for the collection page) and, when Google Analytics 4 is connected, revenue and organic_revenue (revenue from Organic Search sessions) — revenue from all products in this collection, wherever the visitor entered the site — each carrying the change vs the comparison period, plus organic_share (organic revenue as a percentage of revenue). Only Shopify sites have collections: for any other site the status is \"not_shopify\". This is the performance angle — to see what to fix on collection pages, use get_collection_issues. Pass the site domain (from list_sites); omit the dates to use the user's default period.",
            "inputSchema": {
                "properties": {
                    "site": {
                        "description": "The site domain — with or without www or https:// (matching ignores them). Use a value from list_sites, e.g. \"example.com\".",
                        "type": "string"
                    },
                    "range": {
                        "description": "PREFERRED. A named period guaranteed to be cached — typically \"last_7_days\", \"last_30_days\" or \"last_90_days\" (use the exact `range` values listed in this site's available_ranges, from list_sites or a previous response). This always resolves to an instant cache hit, so use it instead of computing start/end yourself whenever the user wants a standard recent window. If you pass range, omit start and end.",
                        "type": "string"
                    },
                    "start": {
                        "description": "Start of a CUSTOM period, as YYYY-MM-DD. Only use start/end for a window not offered in available_ranges; otherwise prefer range. Do not invent dates for \"last 30 days\" — use range, or copy the exact start/end from an available_ranges entry. Omit start, end and range to use the user's default period (also a guaranteed hit).",
                        "type": "string"
                    },
                    "end": {
                        "description": "End of a custom period, as YYYY-MM-DD. Search Console lags a few days, so the most recent usable end is what available_ranges reports — don't assume today. Required whenever start is given.",
                        "type": "string"
                    },
                    "compare_start": {
                        "description": "Optional comparison-period start (YYYY-MM-DD), e.g. for year-over-year. If omitted, the immediately preceding period of the same length is used. Provide both compare_start and compare_end, or neither.",
                        "type": "string"
                    },
                    "compare_end": {
                        "description": "Optional comparison-period end (YYYY-MM-DD). Provide both compare_start and compare_end, or neither.",
                        "type": "string"
                    },
                    "context": {
                        "description": "Briefly, in your own words, why you are calling this tool — the question or task the user is trying to answer. Helps SiteGuru understand usage and improve.",
                        "type": "string"
                    }
                },
                "type": "object",
                "required": [
                    "site"
                ]
            },
            "annotations": {
                "readOnlyHint": true,
                "destructiveHint": false,
                "openWorldHint": false
            },
            "outputSchema": {
                "type": "object",
                "description": "This tool's structured data. The exact keys depend on the tool; the same data is also returned as a human-readable summary in the text content.",
                "additionalProperties": true
            }
        },
        {
            "name": "get_collection_issues",
            "title": "Get Collection Issues",
            "description": "Find the Shopify collections (category pages) that need SEO work — collections ranked by health score, lowest (most issues) first, each with its failing checks (title, meta description, H1, thin content, description relevance, internal links and more). When a collection shares most of its products with higher-revenue collections, its duplicate_collections check lists them under `overlaps_with` (name, handle, url, overlap_pct). Pass a collection handle (from the list) to drill into that one collection's full SEO checklist. The list is paginated: the response carries a `pagination` block with the total and a `next_offset`; to read every collection, keep calling with offset = next_offset until has_more is false. Only Shopify sites have collections: for any other site the status is \"not_shopify\". This is the \"what to fix\" angle — to rank collections by traffic or sales, use get_top_collections. Pass the site domain (from list_sites).",
            "inputSchema": {
                "properties": {
                    "site": {
                        "description": "The site domain (exactly as returned by list_sites).",
                        "type": "string"
                    },
                    "collection": {
                        "description": "Optional. A collection handle (from the list) to drill into its full SEO checklist. Omit to list the collections with the most issues.",
                        "type": "string"
                    },
                    "limit": {
                        "description": "Optional. Page size: maximum number of collections to return in one call (default 50, max 200).",
                        "type": "integer"
                    },
                    "offset": {
                        "description": "Optional. Number of collections to skip before returning results (default 0). To read the whole list, pass the `next_offset` from the previous response's `pagination` block; stop when has_more is false.",
                        "type": "integer"
                    },
                    "context": {
                        "description": "Briefly, in your own words, why you are calling this tool — the question or task the user is trying to answer. Helps SiteGuru understand usage and improve.",
                        "type": "string"
                    }
                },
                "type": "object",
                "required": [
                    "site"
                ]
            },
            "annotations": {
                "readOnlyHint": true,
                "destructiveHint": false,
                "openWorldHint": false
            },
            "outputSchema": {
                "type": "object",
                "description": "This tool's structured data. The exact keys depend on the tool; the same data is also returned as a human-readable summary in the text content.",
                "additionalProperties": true
            }
        },
        {
            "name": "get_backlink_opportunities",
            "title": "Get Backlink Opportunities",
            "description": "Read the backlink opportunities from the competitor backlink analysis that the user ran in SiteGuru for one of their own sites: domains that link to the competitor sites the user chose, and that could be worth asking for a link. This tool only reads the stored analysis; it does not fetch new data. If there is no analysis from the last 30 days, the response has status \"no_data\" and an opportunities_url where the user can choose competitors and run the analysis in SiteGuru. Otherwise the response lists the competitors and up to 50 domains, sorted by rank, with spammy domains and domains whose links are all broken removed. For each domain: rank (domain authority from DataForSEO, 0 to 1000, higher is stronger), links_to_competitors (how many of the competitors it links to), referring_domains, backlinks, spam_score (0 to 100, lower is better), broken and first_seen. Rank is the best quality signal; a high backlink count from few referring domains is weak. Pass the site domain from list_sites.",
            "inputSchema": {
                "properties": {
                    "site": {
                        "description": "The site domain (from list_sites), e.g. \"example.com\". With or without www or https://.",
                        "type": "string"
                    },
                    "context": {
                        "description": "Briefly, in your own words, why you are calling this tool — the question or task the user is trying to answer. Helps SiteGuru understand usage and improve.",
                        "type": "string"
                    }
                },
                "type": "object",
                "required": [
                    "site"
                ]
            },
            "annotations": {
                "readOnlyHint": true,
                "destructiveHint": false,
                "openWorldHint": false
            },
            "outputSchema": {
                "type": "object",
                "description": "This tool's structured data. The exact keys depend on the tool; the same data is also returned as a human-readable summary in the text content.",
                "additionalProperties": true
            }
        },
        {
            "name": "list_competitor_analyses",
            "title": "List Competitor Analyses",
            "description": "List the competitor analyses that the user ran in SiteGuru for one of their own sites, one entry per competitor (the most recent analysis). Use it to find which competitors have an analysis before calling get_competitor_analysis. This tool only reads stored analyses; it does not run new ones. If the site has none, the response has status \"no_data\" and an analyses_url where the user can run an analysis in SiteGuru. Each entry has competitor_domain, the market (location_code and language_code are DataForSEO codes, for example 2840 is the US and 2528 is the Netherlands), gap_count, overlap_count, the period compared, analyzed_on and is_outdated (true when the analysis is more than 30 days old; the data can still be used, but suggest that the user refreshes it in SiteGuru). Pass the site domain from list_sites.",
            "inputSchema": {
                "properties": {
                    "site": {
                        "description": "The site domain (from list_sites), e.g. \"example.com\". With or without www or https://.",
                        "type": "string"
                    },
                    "context": {
                        "description": "Briefly, in your own words, why you are calling this tool — the question or task the user is trying to answer. Helps SiteGuru understand usage and improve.",
                        "type": "string"
                    }
                },
                "type": "object",
                "required": [
                    "site"
                ]
            },
            "annotations": {
                "readOnlyHint": true,
                "destructiveHint": false,
                "openWorldHint": false
            },
            "outputSchema": {
                "type": "object",
                "description": "This tool's structured data. The exact keys depend on the tool; the same data is also returned as a human-readable summary in the text content.",
                "additionalProperties": true
            }
        },
        {
            "name": "get_competitor_analysis",
            "title": "Get Competitor Analysis",
            "description": "Read one competitor analysis that the user ran in SiteGuru for one of their own sites. It contains the keyword gap (keywords where the competitor ranks on page 1 and the site does not rank or ranks low, which shows content to create), the keyword overlap (keywords both sites rank for, and which site ranks higher), an AI summary with recommendations, and a comparison of the two sites: estimated organic traffic, keyword counts, domain rank, referring domains and backlinks. Call list_competitor_analyses first to find the competitor domain. This tool only reads the most recent stored analysis; it does not run a new one. If there is no analysis for that competitor, the response has status \"no_data\" and an analyses_url where the user can run it in SiteGuru. If is_outdated is true, the analysis is more than 30 days old: the data can still be used, but tell the user and give them refresh_url to refresh it in SiteGuru. Most figures are estimates from DataForSEO; only the site's own positions come from Google Search Console. Pass the site domain and the competitor domain.",
            "inputSchema": {
                "properties": {
                    "site": {
                        "description": "The site domain (from list_sites), e.g. \"example.com\". With or without www or https://.",
                        "type": "string"
                    },
                    "competitor": {
                        "description": "The competitor domain to get the analysis for, as shown by list_competitor_analyses, e.g. \"competitor.com\".",
                        "type": "string"
                    },
                    "context": {
                        "description": "Briefly, in your own words, why you are calling this tool — the question or task the user is trying to answer. Helps SiteGuru understand usage and improve.",
                        "type": "string"
                    }
                },
                "type": "object",
                "required": [
                    "site",
                    "competitor"
                ]
            },
            "annotations": {
                "readOnlyHint": true,
                "destructiveHint": false,
                "openWorldHint": false
            },
            "outputSchema": {
                "type": "object",
                "description": "This tool's structured data. The exact keys depend on the tool; the same data is also returned as a human-readable summary in the text content.",
                "additionalProperties": true
            }
        },
        {
            "name": "get_declining_content",
            "title": "Get Declining Content",
            "description": "Find declining content (content decay) for a site — pages whose Google Search Console clicks have meaningfully dropped over the last 6 complete months, biggest decline first. This is a long-term monthly trend, NOT a date-range view: it reveals gradual decay a 30-day comparison would miss, so it takes no dates. The response lists the 6 `months` (oldest first); each page has a `clicks` map of month (YYYY-MM) to clicks (a null value = that month has not been fetched yet, distinct from 0 clicks; a trailing not-yet-fetched month is omitted entirely), plus net_change and percent_change (negative = declined) and its oldest/newest month clicks. Refresh these pages to reclaim lost traffic. Pass the site domain (from list_sites). If the 6-month history is still being gathered, data_status is \"not_collected\".",
            "inputSchema": {
                "properties": {
                    "site": {
                        "description": "The site domain (from list_sites), e.g. \"example.com\". With or without www or https:// — matching ignores them.",
                        "type": "string"
                    },
                    "context": {
                        "description": "Briefly, in your own words, why you are calling this tool — the question or task the user is trying to answer. Helps SiteGuru understand usage and improve.",
                        "type": "string"
                    }
                },
                "type": "object",
                "required": [
                    "site"
                ]
            },
            "annotations": {
                "readOnlyHint": true,
                "destructiveHint": false,
                "openWorldHint": false
            },
            "outputSchema": {
                "type": "object",
                "description": "This tool's structured data. The exact keys depend on the tool; the same data is also returned as a human-readable summary in the text content.",
                "additionalProperties": true
            }
        },
        {
            "name": "get_growing_content",
            "title": "Get Growing Content",
            "description": "Find growing content (rising content) for a site — pages whose Google Search Console clicks have meaningfully grown over the last 6 complete months, biggest growth first. This is a long-term monthly trend, NOT a date-range view, so it takes no dates. The response lists the 6 `months` (oldest first); each page has a `clicks` map of month (YYYY-MM) to clicks (a null value = that month has not been fetched yet, distinct from 0 clicks; a trailing not-yet-fetched month is omitted entirely), plus net_change and percent_change (positive = grew) and its oldest/newest month clicks. These are pages with momentum worth doubling down on. Pass the site domain (from list_sites). If the 6-month history is still being gathered, data_status is \"not_collected\".",
            "inputSchema": {
                "properties": {
                    "site": {
                        "description": "The site domain (from list_sites), e.g. \"example.com\". With or without www or https:// — matching ignores them.",
                        "type": "string"
                    },
                    "context": {
                        "description": "Briefly, in your own words, why you are calling this tool — the question or task the user is trying to answer. Helps SiteGuru understand usage and improve.",
                        "type": "string"
                    }
                },
                "type": "object",
                "required": [
                    "site"
                ]
            },
            "annotations": {
                "readOnlyHint": true,
                "destructiveHint": false,
                "openWorldHint": false
            },
            "outputSchema": {
                "type": "object",
                "description": "This tool's structured data. The exact keys depend on the tool; the same data is also returned as a human-readable summary in the text content.",
                "additionalProperties": true
            }
        },
        {
            "name": "get_declining_keywords",
            "title": "Get Declining Keywords",
            "description": "Find declining keywords for a site — Google Search Console queries whose clicks have meaningfully dropped over the last 6 complete months, biggest decline first. Includes lost keywords: queries that had clicks and now get none (`lost` is true). This is a long-term monthly trend, NOT a date-range view, so it takes no dates. The response lists the 6 `months` (oldest first); each keyword has a `clicks` map of month (YYYY-MM) to clicks (a null value = that month has not been fetched yet, distinct from 0 clicks; a trailing not-yet-fetched month is omitted entirely), plus net_change and percent_change (negative = declined) and its oldest/newest month clicks. Up to 50 are returned; total_keywords gives the full count. Review the pages that rank for these keywords before they drop further. Pass the site domain (from list_sites). If the 6-month history is still being gathered, data_status is \"not_collected\".",
            "inputSchema": {
                "properties": {
                    "site": {
                        "description": "The site domain (from list_sites), e.g. \"example.com\". With or without www or https:// — matching ignores them.",
                        "type": "string"
                    },
                    "context": {
                        "description": "Briefly, in your own words, why you are calling this tool — the question or task the user is trying to answer. Helps SiteGuru understand usage and improve.",
                        "type": "string"
                    }
                },
                "type": "object",
                "required": [
                    "site"
                ]
            },
            "annotations": {
                "readOnlyHint": true,
                "destructiveHint": false,
                "openWorldHint": false
            },
            "outputSchema": {
                "type": "object",
                "description": "This tool's structured data. The exact keys depend on the tool; the same data is also returned as a human-readable summary in the text content.",
                "additionalProperties": true
            }
        },
        {
            "name": "get_growing_keywords",
            "title": "Get Growing Keywords",
            "description": "Find growing keywords for a site — Google Search Console queries whose clicks have meaningfully grown over the last 6 complete months, biggest growth first. Includes new keywords: queries that had no clicks in the oldest month (`new` is true). This is a long-term monthly trend, NOT a date-range view, so it takes no dates. The response lists the 6 `months` (oldest first); each keyword has a `clicks` map of month (YYYY-MM) to clicks (a null value = that month has not been fetched yet, distinct from 0 clicks; a trailing not-yet-fetched month is omitted entirely), plus net_change and percent_change (positive = grew; null when the oldest month had 0 clicks) and its oldest/newest month clicks. Up to 50 are returned; total_keywords gives the full count. Strengthening the pages that rank for these keywords is usually the quickest way to grow. Pass the site domain (from list_sites). If the 6-month history is still being gathered, data_status is \"not_collected\".",
            "inputSchema": {
                "properties": {
                    "site": {
                        "description": "The site domain (from list_sites), e.g. \"example.com\". With or without www or https:// — matching ignores them.",
                        "type": "string"
                    },
                    "context": {
                        "description": "Briefly, in your own words, why you are calling this tool — the question or task the user is trying to answer. Helps SiteGuru understand usage and improve.",
                        "type": "string"
                    }
                },
                "type": "object",
                "required": [
                    "site"
                ]
            },
            "annotations": {
                "readOnlyHint": true,
                "destructiveHint": false,
                "openWorldHint": false
            },
            "outputSchema": {
                "type": "object",
                "description": "This tool's structured data. The exact keys depend on the tool; the same data is also returned as a human-readable summary in the text content.",
                "additionalProperties": true
            }
        },
        {
            "name": "get_page_content_trend",
            "title": "Get Page Content Trend",
            "description": "Drill into ONE page's keyword trends over the last 6 complete months — the \"why\" behind a declining or growing page from get_declining_content / get_growing_content. Pass the site domain and the page `page` (a path like \"/blog/post\", e.g. copied from those tools). Returns the 6 `months` (oldest first) and the page's keywords, biggest movers first; each keyword has a `clicks` map of month (YYYY-MM) to clicks plus net_change and percent_change (negative = declined, positive = grew). Note: keyword totals can be lower than the page total — Google hides rare queries from the per-keyword breakdown. This keyword data is gathered on demand: if it has not been fetched for this page yet, the response has keywords_status \"fetching\" — tell the user it is being gathered (usually a few seconds) and to ask again shortly; do not retry in a loop.",
            "inputSchema": {
                "properties": {
                    "site": {
                        "description": "The site domain (from list_sites), e.g. \"example.com\".",
                        "type": "string"
                    },
                    "page": {
                        "description": "The page path to drill into, e.g. \"/blog/post\" — copy it from a get_declining_content / get_growing_content result. A full URL is also accepted.",
                        "type": "string"
                    },
                    "context": {
                        "description": "Briefly, in your own words, why you are calling this tool — the question or task the user is trying to answer. Helps SiteGuru understand usage and improve.",
                        "type": "string"
                    }
                },
                "type": "object",
                "required": [
                    "site",
                    "page"
                ]
            },
            "annotations": {
                "readOnlyHint": true,
                "destructiveHint": false,
                "openWorldHint": false
            },
            "outputSchema": {
                "type": "object",
                "description": "This tool's structured data. The exact keys depend on the tool; the same data is also returned as a human-readable summary in the text content.",
                "additionalProperties": true
            }
        },
        {
            "name": "get_google_updates",
            "title": "Get Google Updates",
            "description": "List Google's confirmed search updates (core, spam and other ranking updates) of the last 14 months, newest first, with each update's title, start date, end date and Google's link. Takes no site. Use it when the user asks whether a traffic or ranking drop was caused by a Google update. Each update that ended at least 4 days ago has windows: the period after the update (start, end) and the same number of days before it (compare_start, compare_end), the same dates as SiteGuru's Google Update report. Pass those four dates unchanged to get_traffic_overview, get_top_pages or get_top_keywords to compare the site before and after the update. An update with status \"not_ended\" is still rolling out and an update with status \"too_recent\" ended less than 4 days ago; neither has windows yet, because Search Console data is not complete. An update with status \"too_old\" has no windows because the period before it is older than the 16 months Search Console keeps.",
            "inputSchema": {
                "properties": {
                    "context": {
                        "description": "Briefly, in your own words, why you are calling this tool — the question or task the user is trying to answer. Helps SiteGuru understand usage and improve.",
                        "type": "string"
                    }
                },
                "type": "object"
            },
            "annotations": {
                "readOnlyHint": true,
                "destructiveHint": false,
                "openWorldHint": false
            },
            "outputSchema": {
                "type": "object",
                "description": "This tool's structured data. The exact keys depend on the tool; the same data is also returned as a human-readable summary in the text content.",
                "additionalProperties": true
            }
        },
        {
            "name": "start_audit",
            "title": "Start Audit",
            "description": "Start a new SEO audit for a site. This is a write action: SiteGuru crawls the site's public pages on the web, then checks every page and its links, the same as the \"Start new audit\" button in the app. An audit takes from a few minutes to a few hours, depending on the size of the site. When it finishes, the to-do list and all reports are replaced with the new results; the previous results are not kept. Use it when the user has fixed issues and wants SiteGuru to check the site again. Pass skip_crawl=true to check the pages SiteGuru already knows without crawling the site again, and skip_link_check=true to skip checking links. The result is \"scheduled\" (the audit was started), \"busy\" (an audit, crawl or link check is already running; nothing was started), \"not_allowed\" (the plan does not allow it, with what the user can do) or \"blocked\" (the site cannot be crawled). After starting an audit, call list_sites to follow it: the audit is done when the site's audit_state is idle. Do not call start_audit again while an audit is running.",
            "inputSchema": {
                "properties": {
                    "site": {
                        "description": "The site domain — with or without www or https:// (matching ignores them). Use a value from list_sites, e.g. \"example.com\".",
                        "type": "string"
                    },
                    "skip_crawl": {
                        "description": "Check the pages SiteGuru already knows without crawling the site again. Defaults to false.",
                        "type": "boolean"
                    },
                    "skip_link_check": {
                        "description": "Skip checking the links on the pages. Defaults to false.",
                        "type": "boolean"
                    },
                    "context": {
                        "description": "Briefly, in your own words, why you are calling this tool — the question or task the user is trying to answer. Helps SiteGuru understand usage and improve.",
                        "type": "string"
                    }
                },
                "type": "object",
                "required": [
                    "site"
                ]
            },
            "annotations": {
                "readOnlyHint": false,
                "destructiveHint": true,
                "openWorldHint": true
            },
            "outputSchema": {
                "type": "object",
                "description": "This tool's structured data. The exact keys depend on the tool; the same data is also returned as a human-readable summary in the text content.",
                "additionalProperties": true
            }
        }
    ]
}