diff --git a/SKALD.md b/SKALD.md index 72cd8e9..c1140cc 100644 --- a/SKALD.md +++ b/SKALD.md @@ -470,6 +470,7 @@ sudo find /var/www/connectors.skaldagent.net/ -type f -exec chmod 644 {} \; | `tavily` | Tavily | `mcp_remote` | `global` | api_key (`{SECRET:tavilyApiKey}` in URL) | `verify.py` (HTTP probe `/search`) | | `gmail` | Gmail | `mcp_local` | `user` | oauth2 (Google) + deliver: `env/google_authorized_user` (via `GMAIL_CREDS_JSON`) | ⏳ Fase 2 — OAuth via loopback listener | | `gcal` | Google Calendar | `mcp_local` | `user` | oauth2 (Google) + deliver: `env/google_authorized_user` (via `GCAL_CREDS_JSON`) | `verify.py` (creds load + API probe) | +| `drive` | Google Drive | `mcp_local` | `user` | oauth2 (Google) + deliver: `env/google_authorized_user` (via `DRIVE_CREDS_JSON`) | `verify.py` (creds load + Drive API probe) | | `email` | Email (IMAP/SMTP) | `mcp_local` | `user` | password (env) | `verify.py` (IMAP+SMTP probe) | | `firecrawl` | Firecrawl | `mcp_local` | `global` | api_key (env) | — | | `http-fetch` | HTTP Fetch | `mcp_local` | `global` | none | — | @@ -477,6 +478,6 @@ sudo find /var/www/connectors.skaldagent.net/ -type f -exec chmod 644 {} \; | `weather` | Weather (Open-Meteo) | `mcp_local` | `global` | none | — | | `wikipedia` | Wikipedia | `mcp_local` | `global` | none | — | | `whatsapp` | WhatsApp | `mcp_local` | `user` | qr | — | -**Stato del verify-before-save in skald**: `exa`, `email`, `tavily`, e `gcal` hanno `verify` completo +**Stato del verify-before-save in skald**: `exa`, `drive`, `email`, `tavily`, e `gcal` hanno `verify` completo (script + JSON output); `gmail` aspetta la Fase 2 (OAuth via loopback listener). Un connector senza `verify` viene attivato senza test — vedi § Senza verify. diff --git a/connectors/connectors.json b/connectors/connectors.json index 0da536d..c465488 100644 --- a/connectors/connectors.json +++ b/connectors/connectors.json @@ -372,6 +372,51 @@ } ] }, + { + "id": "serpapi-flights", + "name": "SerpAPI Flights", + "type": "mcp_remote", + "scope": "global", + "icon_small": "serpapi-flights/icon_sm.svg", + "icon_large": "serpapi-flights/icon_lg.svg", + "user_description": "Search and compare flights, lookup airport codes, and explore routes via SerpAPI\u2019s Google Flights integration.", + "tags": [ + "flights", + "travel", + "mcp", + "remote", + "search" + ], + "auth": { + "type": "api_key" + }, + "folder": "serpapi-flights", + "version": 1, + "version_string": "1.0.0", + "version_release_date": "2026-07-19", + "files": [ + { + "path": "connector.json", + "sha256": "7c2bd621f461001483787036c5692021f34f3838b90e215be00cae470103526a", + "size": 1352 + }, + { + "path": "verify.py", + "sha256": "eca6298a01cc1c8ce2aece9170a0b228efda7cb8a5c469d1ca3b62932fb7ea51", + "size": 2003 + }, + { + "path": "icon_sm.svg", + "sha256": "6dbd397e7e06d0431089b12e590c0a5dafa362dcb7117af122e240ea9231edc3", + "size": 570 + }, + { + "path": "icon_lg.svg", + "sha256": "5a2ffa7f2502fd27dc0027395db2c863bb544c8de1eef8131a5228d87402c8e6", + "size": 574 + } + ] + }, { "id": "ssh", "name": "SSH Remote Access", diff --git a/connectors/serpapi-flights/connector.json b/connectors/serpapi-flights/connector.json new file mode 100644 index 0000000..6490395 --- /dev/null +++ b/connectors/serpapi-flights/connector.json @@ -0,0 +1,47 @@ +{ + "id": "serpapi-flights", + "name": "SerpAPI Flights", + "version": 1, + "version_string": "1.0.0", + "version_release_date": "2026-07-19", + "type": "mcp_remote", + "scope": "global", + "tags": [ + "flights", + "travel", + "mcp", + "remote", + "search" + ], + "docs": [ + { + "lang": "en", + "description": "Search and compare flights, lookup airport codes, explore routes and prices via SerpAPI's Google Flights integration. Covers departure/arrival search, price comparison, and routing across dates.", + "llm_short_description": "Flight search via SerpAPI: search flights, lookup airport codes, compare prices and routes using Google Flights data." + } + ], + "auth": { + "type": "api_key" + }, + "env": [ + { + "name": "serpapiApiKey", + "label": "SerpAPI API key", + "description": "Get one at https://serpapi.com/manage-api-key (free plan includes 100 searches/month).", + "required": true, + "secret": true, + "example": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6" + } + ], + "mcp_config": { + "url": "https://mcp.serpapi.com/{SECRET:serpapiApiKey}/mcp", + "transport": "streamable-http" + }, + "verify": { + "command": "python3 verify.py", + "timeout_secs": 15 + }, + "homepage": "https://serpapi.com/google-flights-api", + "icon_small": "icon_sm.svg", + "icon_large": "icon_lg.svg" +} diff --git a/connectors/serpapi-flights/icon_lg.svg b/connectors/serpapi-flights/icon_lg.svg new file mode 100644 index 0000000..920e79d --- /dev/null +++ b/connectors/serpapi-flights/icon_lg.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/connectors/serpapi-flights/icon_sm.svg b/connectors/serpapi-flights/icon_sm.svg new file mode 100644 index 0000000..b633d64 --- /dev/null +++ b/connectors/serpapi-flights/icon_sm.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/connectors/serpapi-flights/verify.py b/connectors/serpapi-flights/verify.py new file mode 100644 index 0000000..a8b2eb1 --- /dev/null +++ b/connectors/serpapi-flights/verify.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +"""Verify SerpAPI Flights MCP connectivity. + +Reads serpapiApiKey from the environment, sends a lightweight JSON-RPC +initialize to https://mcp.serpapi.com/{key}/mcp, and prints: + + {"ok": true, "message": "SerpAPI Flights MCP endpoint is reachable"} + {"ok": false, "message": "SerpAPI Flights MCP endpoint is unreachable: "} + +stdlib only (urllib). +""" +import json +import os +import sys +import urllib.error +import urllib.request + + +def _result(ok, message): + print(json.dumps({"ok": ok, "message": message})) + sys.exit(0 if ok else 1) + + +def main(): + api_key = os.environ.get("serpapiApiKey", "").strip() + if not api_key: + api_key = os.environ.get("SERPAPI_API_KEY", "").strip() + if not api_key: + _result(False, "No SerpAPI API key provided (serpapiApiKey env var is empty)") + + url = f"https://mcp.serpapi.com/{api_key}/mcp" + + body = json.dumps({ + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": { + "protocolVersion": "2025-03-26", + "capabilities": {}, + "clientInfo": {"name": "skald-verify", "version": "1.0.0"}, + }, + }).encode("utf-8") + + req = urllib.request.Request( + url, + data=body, + headers={"Content-Type": "application/json"}, + method="POST", + ) + + try: + with urllib.request.urlopen(req, timeout=15) as resp: + if 200 <= resp.status < 300: + _result(True, "SerpAPI Flights MCP endpoint is reachable") + _result(False, f"SerpAPI returned HTTP {resp.status}") + except urllib.error.HTTPError as e: + if e.code in (401, 403): + _result(False, "SerpAPI API key is invalid or unauthorized " + f"(HTTP {e.code})") + _result(False, f"SerpAPI returned HTTP {e.code}: {e.reason}") + except Exception as e: + _result(False, f"SerpAPI Flights MCP endpoint is unreachable: {e}") + + +if __name__ == "__main__": + main()