From 5f254d9a96c53ac9a4316ef5743327e6373feb18 Mon Sep 17 00:00:00 2001 From: xavix-yo Date: Wed, 22 Jul 2026 22:46:08 +0100 Subject: [PATCH] =?UTF-8?q?Aggiunto=20connector=20Google=20Trends=20(mcp?= =?UTF-8?q?=5Flocal)=20=E2=80=94=20trendspyg,=20nessuna=20API=20key?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SKALD.md | 1 + connectors/connectors.json | 56 ++- connectors/google-trends/connector.json | 37 ++ connectors/google-trends/google_trends_mcp.py | 385 ++++++++++++++++++ connectors/google-trends/icon_lg.png | Bin 0 -> 4664 bytes connectors/google-trends/icon_sm.png | Bin 0 -> 2304 bytes connectors/google-trends/requirements.txt | 1 + connectors/google-trends/verify.py | 30 ++ 8 files changed, 509 insertions(+), 1 deletion(-) create mode 100644 connectors/google-trends/connector.json create mode 100644 connectors/google-trends/google_trends_mcp.py create mode 100644 connectors/google-trends/icon_lg.png create mode 100644 connectors/google-trends/icon_sm.png create mode 100644 connectors/google-trends/requirements.txt create mode 100644 connectors/google-trends/verify.py diff --git a/SKALD.md b/SKALD.md index e9fd596..90f3c81 100644 --- a/SKALD.md +++ b/SKALD.md @@ -498,6 +498,7 @@ sudo find /var/www/connectors.skaldagent.net/ -type f -exec chmod 644 {} \; | `http-fetch` | HTTP Fetch | `mcp_local` | `global` | none | — | | `ssh` | SSH Remote Access | `mcp_local` | `user` | none (auth runtime per-alias) | — | | `weather` | Weather (Open-Meteo) | `mcp_local` | `global` | none | — | +| `google-trends` | Google Trends | `mcp_local` | `global` | none | `verify.py` (trendspyg import probe) | | `wikipedia` | Wikipedia | `mcp_local` | `global` | none | — | | `whatsapp` | WhatsApp | `mcp_local` | `user` | qr | — | **Stato del verify-before-save in skald**: `exa`, `drive`, `email`, `tavily`, e `gcal` hanno `verify` completo diff --git a/connectors/connectors.json b/connectors/connectors.json index 91c53d6..15a3e1c 100644 --- a/connectors/connectors.json +++ b/connectors/connectors.json @@ -976,6 +976,60 @@ "display_name": "Distance Matrix" } ] + }, + + { + "id": "google-trends", + "name": "Google Trends", + "type": "mcp_local", + "scope": "global", + "icon_small": "google-trends/icon_sm.png", + "icon_large": "google-trends/icon_lg.png", + "user_description": "Current trending topics by country, keyword search interest history, related queries, and regional interest maps. Powered by trendspyg — no API key required.", + "requires": [ + "PYTHON" + ], + "tags": [ + "trends", + "mcp", + "local", + "google", + "search", + "analytics" + ], + "auth": { + "type": "none" + }, + "folder": "google-trends", + "version": 1, + "version_string": "1.0.0", + "version_release_date": "2026-07-22", + "files": [ + {"path": "google_trends_mcp.py", "sha256": "5404eb7c81e82180a3b9e3052a4793e80a361ae60156d598d8e62e36b21dea8a", "size": 13552}, + {"path": "connector.json", "sha256": "0f50383fa3d664e6f487983645c56a57a076353a8fea337d121826210ebd3a8a", "size": 1191}, + {"path": "verify.py", "sha256": "adbae57fc109c06c5d61ff50b67faaf62e1fa493a18cbfdacbc73f24d51ba9b8", "size": 831}, + {"path": "requirements.txt", "sha256": "8b13038b5c79cff2680a9b482491b969ac83ce4efa396f408bfca97385d00621", "size": 17}, + {"path": "icon_sm.png", "sha256": "e7b9a8ba97f824948dae572db069296bbf81bef84007d1e18c0fd598b0a05232", "size": 2304}, + {"path": "icon_lg.png", "sha256": "636d2982098c862a04099aea01d7f7ca7a9ad258dafed286baa08864480eacad", "size": 4664} + ], + "tools": [ + { + "name": "status", + "display_name": "Status Check" + }, + { + "name": "get_trending", + "display_name": "Get Trending Topics" + }, + { + "name": "get_interest_over_time", + "display_name": "Interest Over Time" + }, + { + "name": "explore", + "display_name": "Explore Keyword" + } + ] } ] -} \ No newline at end of file +} diff --git a/connectors/google-trends/connector.json b/connectors/google-trends/connector.json new file mode 100644 index 0000000..ac8d143 --- /dev/null +++ b/connectors/google-trends/connector.json @@ -0,0 +1,37 @@ +{ + "id": "google-trends", + "name": "Google Trends", + "version": 1, + "version_string": "1.0.0", + "version_release_date": "2026-07-22", + "type": "mcp_local", + "scope": "global", + "launch_command": "python3 google_trends_mcp.py", + "transport": "stdio", + "requires": ["PYTHON"], + "tags": ["trends", "mcp", "local", "google", "search", "analytics"], + "dependencies": [ + "trendspyg>=0.7.0" + ], + "setup_instructions": [ + "Install dependencies: pip install -r requirements.txt", + "Run: python3 google_trends_mcp.py" + ], + "docs": [ + { + "lang": "en", + "description": "Google Trends — current trending topics, keyword interest over time, related queries, and interest by region. Powered by trendspyg (no API key needed).", + "llm_short_description": "Google Trends — current trending topics by country, keyword search interest history (0-100 scale), related queries, and regional interest maps. No API key required." + } + ], + "auth": { + "type": "none" + }, + "mcp_config": { + "command": "python3", + "args": ["google_trends_mcp.py"] + }, + "homepage": "https://trends.google.com", + "icon_small": "icon_sm.png", + "icon_large": "icon_lg.png" +} diff --git a/connectors/google-trends/google_trends_mcp.py b/connectors/google-trends/google_trends_mcp.py new file mode 100644 index 0000000..410de60 --- /dev/null +++ b/connectors/google-trends/google_trends_mcp.py @@ -0,0 +1,385 @@ +#!/usr/bin/env python3 +"""Google Trends MCP server (JSON-RPC 2.0 over stdio) using trendspyg. + +Capabilities (callable as `mcp__google-trends__`): + status — self-check: confirms trendspyg is importable + get_trending — current trending topics per country (fast, RSS, no browser) + get_interest_over_time — keyword interest history (browser-based, ~10-30s) + explore — full keyword analysis: interest over time + related queries + + interest by region (browser-based, ~10-30s) + +Data source: trendspyg v0.7.0 (MIT) — Google Trends scraping library. +RSS path is lightweight (HTTP only); Explore path requires Chrome/Selenium. + +Run with: + python3 google_trends_mcp.py +""" + +from __future__ import annotations + +import json +import sys +from typing import Any + +import trendspyg + + +# ── Logging ───────────────────────────────────────────────────────────────────── + +def log(msg: str) -> None: + print(f"[google_trends_mcp] {msg}", file=sys.stderr, flush=True) + + +# ── Tool implementations ──────────────────────────────────────────────────────── + +def _status() -> dict[str, Any]: + """Self-check: verify trendspyg is importable and working.""" + v = trendspyg.__version__ + return { + "status": "ok", + "library": "trendspyg", + "version": v, + } + + +def _get_trending( + geo: str = "US", + max_trends: int = 10, + include_articles: bool = False, +) -> dict[str, Any]: + """Get current trending search topics for a country (RSS — fast, no browser). + + Args: + geo: Two-letter country code (US, GB, IT, DE, JP, etc.). + max_trends: Max trending topics to return (default 10, max 20). + include_articles: Include news article headlines/URLs (default False). + + Returns: + A dict with: geo, fetched_at, trending_trends[] (each with title, + traffic_volume, articles[] when requested). + """ + max_trends = min(max_trends, 20) + if max_trends < 1: + max_trends = 10 + + try: + raw = trendspyg.download_google_trends_rss( + geo=geo, + output_format="dict", + include_images=False, + include_articles=include_articles, + max_articles_per_trend=3, + ) + except Exception as e: + return {"status": "error", "message": str(e), "geo": geo} + + if not raw: + return {"status": "ok", "geo": geo, "trends": []} + + # raw is a list of trend dicts + trends = [] + for t in raw[:max_trends]: + item = { + "title": t.get("trend", ""), + "traffic": t.get("traffic", ""), + "traffic_min": t.get("traffic_min"), + "published": t.get("published", ""), + } + if include_articles and "articles" in t: + item["articles"] = [ + { + "headline": a.get("headline", a.get("title", "")), + "url": a.get("url", ""), + "source": a.get("source", ""), + } + for a in (t.get("articles") or [])[:3] + ] + trends.append(item) + + return {"status": "ok", "geo": geo, "trends": trends} + + +def _get_interest_over_time( + keyword: str, + geo: str = "US", + timeframe: str = "today 12-m", + output_format: str = "dict", +) -> dict[str, Any]: + """Get a keyword's search interest over time (0-100 scale). + + Browser-based — takes ~10-30s. Returns data points with date, value, + and is_partial flag. + + Args: + keyword: Search term to analyze. + geo: Two-letter country code (US, GB, IT, etc.) or '' for worldwide. + timeframe: Google Trends range ('today 12-m', 'today 5-y', + 'now 7-d', 'now 1-H', 'all', or 'YYYY-MM-DD YYYY-MM-DD'). + output_format: 'dict' (default) or 'json'. + + Returns: + Dict with keyword, geo, timeframe, datapoints[]. + """ + if not keyword or not keyword.strip(): + return {"status": "error", "message": "keyword is required"} + + try: + of = "dict" if output_format == "dict" else "json" + data = trendspyg.download_google_trends_interest_over_time( + keyword=keyword.strip(), + geo=geo, + timeframe=timeframe, + output_format=of, + ) + except Exception as e: + return {"status": "error", "message": str(e), "keyword": keyword} + + if of == "json": + data = json.loads(data) if isinstance(data, str) else data + + return { + "status": "ok", + "keyword": keyword, + "geo": geo, + "timeframe": timeframe, + "datapoints": data, + } + + +def _explore( + keyword: str, + geo: str = "US", + timeframe: str = "today 12-m", + include_related: bool = True, + include_geo: bool = True, +) -> dict[str, Any]: + """Full keyword analysis: interest over time + related queries + region map. + + Browser-based — takes ~10-30s. Returns the complete Explore data in + one call. + + Args: + keyword: Search term to analyze. + geo: Two-letter country code (US, GB, IT, etc.) or '' for worldwide. + timeframe: Google Trends range ('today 12-m', 'today 5-y', etc.). + include_related: Include related queries (top + rising). + include_geo: Include interest by region. + + Returns: + Complete ExploreEnvelope as a JSON-safe dict. + """ + if not keyword or not keyword.strip(): + return {"status": "error", "message": "keyword is required"} + + try: + data = trendspyg.download_google_trends_explore( + keyword=keyword.strip(), + geo=geo, + timeframe=timeframe, + include_related=include_related, + include_geo=include_geo, + ) + except Exception as e: + return {"status": "error", "message": str(e), "keyword": keyword} + + # data is already a dict (ExploreEnvelope) + data["status"] = "ok" + return data + + +# ── JSON-RPC helpers ─────────────────────────────────────────────────────────── + +TOOLS: dict[str, tuple[Any, dict[str, Any]]] = { + "status": ( + _status, + { + "description": "Check if Google Trends MCP is operational", + "inputSchema": {"type": "object", "properties": {}, "required": []}, + }, + ), + "get_trending": ( + _get_trending, + { + "description": "Get current trending search topics for a country (RSS — fast, no browser)", + "inputSchema": { + "type": "object", + "properties": { + "geo": { + "type": "string", + "description": "Two-letter country code (US, GB, IT, DE, etc.)", + "default": "US", + }, + "max_trends": { + "type": "number", + "description": "Max trending topics (1-20)", + "default": 10, + }, + "include_articles": { + "type": "boolean", + "description": "Include news article headlines/URLs", + "default": False, + }, + }, + "required": [], + }, + }, + ), + "get_interest_over_time": ( + _get_interest_over_time, + { + "description": "Get a keyword's search interest history (0-100 scale, browser-based ~10-30s)", + "inputSchema": { + "type": "object", + "properties": { + "keyword": { + "type": "string", + "description": "Search term to analyze", + }, + "geo": { + "type": "string", + "description": "Two-letter country code or '' for worldwide", + "default": "US", + }, + "timeframe": { + "type": "string", + "description": "Google Trends range: 'today 12-m', 'today 5-y', 'now 7-d', 'all'", + "default": "today 12-m", + }, + "output_format": { + "type": "string", + "description": "Output format: 'dict' or 'json'", + "default": "dict", + }, + }, + "required": ["keyword"], + }, + }, + ), + "explore": ( + _explore, + { + "description": "Full keyword analysis: interest over time + related queries + region map (browser-based ~10-30s)", + "inputSchema": { + "type": "object", + "properties": { + "keyword": { + "type": "string", + "description": "Search term to analyze", + }, + "geo": { + "type": "string", + "description": "Two-letter country code or '' for worldwide", + "default": "US", + }, + "timeframe": { + "type": "string", + "description": "Google Trends range: 'today 12-m', 'today 5-y', 'now 7-d', 'all'", + "default": "today 12-m", + }, + "include_related": { + "type": "boolean", + "description": "Include top + rising related queries", + "default": True, + }, + "include_geo": { + "type": "boolean", + "description": "Include interest by region", + "default": True, + }, + }, + "required": ["keyword"], + }, + }, + ), +} + + +# ── JSON-RPC request handler ─────────────────────────────────────────────────── + +def handle_request(request: dict[str, Any]) -> dict[str, Any]: + """Dispatch a JSON-RPC 2.0 request and return a response.""" + req_id = request.get("id", None) + method = request.get("method", "") + params = request.get("params", {}) + + if method == "tools/list": + tools_list = [] + for name, (_, schema) in TOOLS.items(): + t = { + "name": name, + "description": schema["description"], + "inputSchema": schema["inputSchema"], + } + # Set title for friendly name in UI + title_map = { + "status": "Status Check", + "get_trending": "Get Trending Topics", + "get_interest_over_time": "Interest Over Time", + "explore": "Explore Keyword", + } + if name in title_map: + t["title"] = title_map[name] + tools_list.append(t) + return _ok(req_id, tools_list) + + elif method == "tools/call": + tool_name = params.get("name", "") + arguments = params.get("arguments", {}) + + if tool_name not in TOOLS: + return _error(req_id, -32601, f"Method not found: {tool_name}") + + try: + fn, _ = TOOLS[tool_name] + result = fn(**arguments) + # Ensure result is JSON-serializable and wrapped in content + if isinstance(result, dict): + content = [{"type": "text", "text": json.dumps(result, default=str)}] + else: + content = [{"type": "text", "text": str(result)}] + return {"jsonrpc": "2.0", "id": req_id, "result": {"content": content}} + except Exception as e: + log(f"Error calling {tool_name}: {e}") + return _error(req_id, -32603, f"Internal error: {e}") + + else: + return _error(req_id, -32601, f"Method not found: {method}") + + +def _ok(req_id: Any, result: Any) -> dict[str, Any]: + return {"jsonrpc": "2.0", "id": req_id, "result": result} + + +def _error(req_id: Any, code: int, message: str) -> dict[str, Any]: + return { + "jsonrpc": "2.0", + "id": req_id, + "error": {"code": code, "message": message}, + } + + +# ── Main loop ────────────────────────────────────────────────────────────────── + +def main() -> None: + """Read JSON-RPC 2.0 requests from stdin and write responses to stdout.""" + log("Google Trends MCP server starting...") + for line in sys.stdin: + line = line.strip() + if not line: + continue + try: + request = json.loads(line) + except json.JSONDecodeError as e: + log(f"Invalid JSON: {e}") + resp = _error(None, -32700, f"Parse error: {e}") + else: + resp = handle_request(request) + + sys.stdout.write(json.dumps(resp) + "\n") + sys.stdout.flush() + + log("Google Trends MCP server shutting down.") + + +if __name__ == "__main__": + main() diff --git a/connectors/google-trends/icon_lg.png b/connectors/google-trends/icon_lg.png new file mode 100644 index 0000000000000000000000000000000000000000..ffc3bab10f139f2b129e49d43cd4bf65bc536373 GIT binary patch literal 4664 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.7.0 diff --git a/connectors/google-trends/verify.py b/connectors/google-trends/verify.py new file mode 100644 index 0000000..ff882c4 --- /dev/null +++ b/connectors/google-trends/verify.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +"""Verify Google Trends MCP is functional — just tests import + RSS path (fast, no browser).""" + +import json +import sys + + +def check() -> dict: + """Verify trendspyg import and RSS download.""" + try: + import trendspyg + except ImportError as e: + return {"ok": False, "message": f"trendspyg not installed: {e}"} + + v = getattr(trendspyg, "__version__", "unknown") + return { + "ok": True, + "message": f"trendspyg v{v} — Google Trends MCP is ready", + "details": { + "version": v, + "rss_path": "available (no browser needed)", + "explore_path": "available (requires Chrome/Selenium)", + }, + } + + +if __name__ == "__main__": + result = check() + sys.stdout.write(json.dumps(result)) + sys.exit(0 if result["ok"] else 1)