# Skald Connectors Marketplace ## Reference locale - **[docs/connector.manifest_guide.md](docs/connector.manifest_guide.md)** — Guida ufficiale per generare connector corretti (copiata da `skald-circle/blueprint/`) _Updated: 2026-07-22_ **Remote**: `https://git.skaldagent.net/dguiducci/skald-connectors.git` **Live**: `https://connectors.skaldagent.net/` **OAuth callback**: `https://connectors.skaldagent.net/oauth/show.html` ## Deploy Il branch **`main`** è il branch di **release**. Solo codice pronto per produzione finisce qui. Sviluppo e versioni alfa-staranno su branch separati in futuro. Deploy rapido con MCP SSH: ```bash mcp__ssh__exec alias=skald-home-server command="/home/dguiducci/marketplace_deploy.sh" ``` Oppure via SSH classico: ```bash ssh dguiducci@skald-home-server /home/dguiducci/marketplace_deploy.sh ``` Lo script sul server fa: 1. `git pull` in `/home/dguiducci/repos/skald-connectors/` 2. `cp -r connectors/*` in `/var/www/connectors.skaldagent.net/` La directory `/var/www/connectors.skaldagent.net/` è di proprietà di `dguiducci`, quindi non serve sudo per la copia. **Prima del deploy**, ricordarsi di rigenerare l'indice: ```bash python3 scripts/compile.py ``` ## Cos'è Il **marketplace** è il catalogo dei connector testati per Skald. Ogni connector è un adattatore che permette a Skald di interfacciarsi con un servizio esterno (API, email, calendario, ricerca, messaggistica, ecc.). Due tipi di connector: - **`mcp_remote`** — un MCP server già hosted, accessibile via URL (es. Tavily). - **`mcp_local`** — script Python/Node da eseguire lato client (es. Gmail, Google Calendar). ## Friendly tool names (2026-07-21) Ogni tool MCP deve esporre un nome friendly per la UI di Skald. Due modi, in ordine di preferenza: 1. **Via script MCP (preferito)** — aggiungere `"title": "Friendly Name"` nella definizione di ogni tool dentro `tools/list`. Funziona per tutti gli script locali (Python/Node) che controlliamo. 2. **Via manifest (fallback)** — aggiungere `"tools": [{"name": "...", "display_name": "..."}]` in `connector.json` **e** in `connectors.json`. Usato solo per connector remoti o package esterni (es. `npx -y firecrawl-mcp`). **Ordine di risoluzione** (Skald li prova in quest'ordine): 1. `tools[].display_name` dal manifest 2. `title` dal `tools/list` dell'MCP server 3. Prettify automatico del nome raw (`send_message` → "Send Message") Stato attuale (2026-07-21): tutti i 13 connector del marketplace hanno `title` nello script o `tools[]` nel manifest. ## Struttura directory ``` connectors/ ├── index.json ← lista ordinata degli id dei connector (input per compile.py) ├── connectors.json ← INDICE COMPILATO (generato da compile.py, non editare) ├── compile.py ← genera connectors.json ├── index.html ← Catalogo UI (legge connectors.json via fetch) ├── oauth/ │ └── show.html ← OAuth callback receiver (client-side, no backend) ├── gmail/ ← Un connector per cartella │ ├── fragment.json ← Frammento dell'indice (id, name, type, ..., SENZA files[]) │ ├── connector.json ← Configurazione tecnica │ ├── gmail_mcp_server.py ← Script MCP │ ├── gmail_oauth_setup.py ← Script setup OAuth │ ├── requirements.txt ← Dipendenze Python │ ├── icon_sm.svg ← Icona piccola (48×48) │ └── icon_lg.svg ← Icona grande (es. 96×96) ├── email/ │ ├── fragment.json │ ├── connector.json │ ├── email_mcp_server.py │ ├── verify.py │ ├── requirements.txt │ ├── icon_sm.svg │ └── icon_lg.svg ├── ssh/ │ ├── fragment.json │ ├── connector.json │ ├── ssh_mcp_server.py │ ├── requirements.txt │ ├── icon_sm.svg │ └── icon_lg.svg └── tavily/ ├── fragment.json ├── connector.json ├── verify.py ├── icon_sm.png └── icon_lg.png ``` └── icon_lg.png ``` ## Schema — connectors.json (root) Questo è l'**unico punto di fiducia**. Contiene `type`, `scope` e gli sha256 dei file di ogni connector. Non ha hash di sé stesso — in futuro potrà essere firmato digitalmente. ```json { "version": 1, "connectors": [ { "id": "gmail", "name": "Gmail", "type": "mcp_local", "scope": "user", "icon_small": "gmail/icon_sm.svg", "icon_large": "gmail/icon_lg.svg", "user_description": "Read, send, and manage Gmail emails via OAuth...", "requires": ["OAUTH", "PYTHON"], "tags": ["email", "mcp", "local", "google"], "auth": { "type": "oauth2", "provider": "google", "scopes": [ "https://www.googleapis.com/auth/gmail.modify", "https://www.googleapis.com/auth/gmail.labels" ] }, "folder": "gmail", "version": 1, "version_string": "1.0.0", "version_release_date": "2026-07-19", "files": [ | `auth` | per OAuth | Oggetto con `type`, `provider`, `scopes` per badge UI (NO `deliver` qui, è nel manifest) | {"path": "gmail_mcp_server.py", "sha256": "a50d4da9621f7a4b092f...", "size": 46772}, {"path": "gmail_oauth_setup.py", "sha256": "e488acb289c43a3e6d54...", "size": 3627}, {"path": "icon_lg.svg", "sha256": "93c8d9c8dae96f0206e5...", "size": 254}, {"path": "icon_sm.svg", "sha256": "029d7f5d81de6cf2b17b...", "size": 251}, {"path": "requirements.txt", "sha256": "3f659cc5e5f0543f1326...", "size": 82} ] } ] } ``` ### Campi dell'indice | Campo | Obbligatorio | Descrizione | |-------|-------------|-------------| | `id` | ✅ | Identificatore unico (kebab-case) | | `name` | ✅ | Nome visualizzato | | `type` | ✅ | `mcp_remote` o `mcp_local` | | `scope` | ✅ | `global` o `user` | | `icon_small` | ✅ | Path relativo dalla root del marketplace | | `icon_large` | ✅ | Path relativo dalla root del marketplace | | `user_description` | ✅ | Descrizione breve per la UI | | `requires` | ✅ | Array di enum requisiti | | `tags` | ✅ | Array di tag per filtraggio | | `folder` | ✅ | Nome della cartella del connector | | `version` | ✅ | Intero per-connector, +1 a ogni modifica dei file | | `version_string` | ✅ | Semver (solo display) | | `version_release_date` | ✅ | Data ISO 8601 YYYY-MM-DD (solo display) | | `files` | ✅ | Array di file con sha256 (NO self-hash) | ## Schema — connector.json (per cartella) Configurazione tecnica per l'attivazione del connector. ```json { "id": "gmail", "name": "Gmail", "version": 1, "version_string": "1.0.0", "version_release_date": "2026-07-19", "type": "mcp_local", "scope": "user", "launch_command": "python3 gmail_mcp_server.py", "transport": "stdio", "requires": ["OAUTH", "PYTHON"], "tags": ["email", "mcp", "local", "google"], "dependencies": [ "google-api-python-client>=2.150.0", "google-auth>=2.35.0", "google-auth-oauthlib>=1.2.0" ], "setup_instructions": [ "Install dependencies: pip install -r requirements.txt", "Run: python3 gmail_oauth_setup.py (optional, for standalone use — Skald handles OAuth)" ], "docs": [ { "lang": "en", "description": "Full description for human users...", "llm_short_description": "Google Calendar — read, create, update, delete, and respond to calendar events. Supports push notifications." } ], "auth": { "type": "oauth2", "provider": "google", "scopes": [ "https://www.googleapis.com/auth/gmail.modify", "https://www.googleapis.com/auth/gmail.labels" ], "deliver": { "as": "env", "format": "google_authorized_user", "env": "GMAIL_CREDS_JSON" } }, "mcp_config": { "command": "python3", "args": ["gmail_mcp_server.py"] }, "homepage": "https://mail.google.com", "icon_small": "icon_sm.svg", "icon_large": "icon_lg.svg" } ``` ### Campi del connector.json | Campo | Obbligatorio | Descrizione | |-------|-------------|-------------| | `id` | ✅ | Identificatore unico (match con folder name) | | `name` | ✅ | Nome visualizzato | | `version` | ✅ | Intero per-connector, +1 a ogni modifica dei file | | `version_string` | ✅ | Semver (solo display) | | `version_release_date` | ✅ | Data ISO 8601 YYYY-MM-DD (solo display) | | `type` | ✅ | `mcp_remote` o `mcp_local` | | `scope` | ✅ | `global` o `user` | | `requires` | ✅ | Array di enum requisiti | | `tags` | ✅ | Array di tag | | `auth` | ✅ | Oggetto configurazione autenticazione | | `docs` | ✅ | Array di documentazione multilingua. **`llm_short_description`** è il campo che finisce nel system prompt dell'LLM — deve descrivere COSA FA il connector, non elencare i tool (l'LLM li vede dopo `activate_tools`). Esempio: *"Weather — current conditions, 16-day forecast, and AQI data for any location."* | `icon_small` | ✅ | Nome file icona nella cartella locale | | `icon_large` | ✅ | Nome file icona nella cartella locale | | `launch_command` | solo `mcp_local` | Comando per avviare il server MCP | | `transport` | solo `mcp_local` | `stdio` (default) | | `dependencies` | consigliato | Dipendenze Python/Node (array vuoto se solo stdlib) | | `env` | se `requires` include `ENV` | Variabili d'ambiente che l'utente deve fornire (schema per la UI) — vedi § Campo env | | `setup_instructions` | consigliato | Passi per configurare il connector | | `mcp_config` | solo `mcp_local` | Configurazione per l'MCP client | | `homepage` | opzionale | URL del servizio | ## Enum riservati ### type (tipo di connector) | Valore | Descrizione | Esempi | |--------|-------------|--------| | `mcp_remote` | Server MCP hosted, accessibile via URL | Tavily, Weather | | `mcp_local` | Script da eseguire localmente | Gmail, Google Calendar, WhatsApp | | `script` | Script standalone (non MCP) | *(futuro)* | ### scope (ambito di configurazione) | Valore | Descrizione | Esempi | |--------|-------------|--------| | `global` | Una singola istanza/config per tutto il sistema | Tavily, Weather, Google Trends | | `user` | Ogni utente ha la propria istanza/autenticazione | Gmail, WhatsApp, Google Calendar | ### requires (prerequisiti) | Valore | Descrizione | |--------|-------------| | `API_KEY` | Richiede una chiave API da configurare | | `OAUTH` | Richiede autenticazione OAuth (Google, ecc.) | | `DOCKER` | Richiede Docker Engine | | `NODE` | Richiede Node.js runtime | | `PYTHON` | Richiede Python 3 | | `SECRETS_DIR` | ❌ **Deprecato** — la cartella `secrets/` è rimossa dal modello; i connector devono usare `ENV`/`SECRET` (vedi § Sintassi placeholder) | | `ENV` | Richiede variabili d'ambiente (dichiarate nel campo `env` del manifest) | ## Campo auth Struttura che descrive come il connector gestisce l'autenticazione: ```json // API key in query string {"type": "api_key", "delivery": "query", "param": "tavilyApiKey"} // API key in header {"type": "api_key", "delivery": "header", "param": "X-API-Key"} // OAuth2 — provider SOLO slug (Skald risolve endpoint + client secrets) {"type": "oauth2", "provider": "google", "scopes": ["...", "..."]} // OAuth2 con deliver (Skald inietta il JSON authorized_user via env var) {"type": "oauth2", "provider": "google", "scopes": ["..."], "deliver": {"as": "env", "format": "google_authorized_user", "env": "GMAIL_CREDS_JSON"}} // OAuth2 con deliver su file (legacy) {"type": "oauth2", "provider": "google", "scopes": ["..."], "deliver": {"as": "file", "format": "google_authorized_user", "path": "{secrets}/gmail_creds.json"}} // Password / app-password fornita via variabili d'ambiente {"type": "password", "delivery": "env"} // Nessuna autenticazione {"type": "none"} ``` ### Campo deliver (solo OAuth2) Dichiara **come** Skald consegna la credenziale OAuth ottenuta al processo del server MCP. | Campo | Obbligatorio | Descrizione | |-------|-------------|-------------| | `as` | ✅ | `"file"` (su disco) o `"env"` (variabile d'ambiente) | | `format` | ✅ | Nome della serializzazione — es. `"google_authorized_user"` (JSON Google che `from_authorized_user_file` legge), `"refresh_token"`, `"access_token"` | | `path` | solo `as=file` | Path con placeholder `{secrets}` (Skald lo espande a dir per-utente a runtime). DEVE matchare il path in `mcp_config.env`. | | `env` | solo `as=env` | Nome della variabile d'ambiente in cui Skald inietta l'intero JSON authorized_user. **Non va dichiarata in `mcp_config.env`** — Skald la inietta a runtime. | Il feed NON contiene MAI: `client_id`, `client_secret`, `endpoint` URL, `redirect_uri`. Questi sono risolti lato Skald a partire dal nome del `provider`. ## Campo env (variabili d'ambiente) Usato quando `requires` include `ENV`. È un **array** che dichiara le variabili d'ambiente che l'utente deve fornire per far funzionare il connector; **nessuna credenziale finisce su disco né in `secrets/`** — l'host raccoglie i valori, obbliga la compilazione dei campi obbligatori, e li inietta come environment nel processo del server MCP al lancio. Il server le legge da `os.environ`. ```json "env": [ { "name": "EMAIL_IMAP_HOST", // nome della variabile d'ambiente "label": "IMAP host", // etichetta per la UI "description": "IMAP server hostname (es. imap.gmail.com)", "required": true, // se true, l'host deve obbligare la compilazione "secret": false, // se true, la UI la maschera e la tratta come segreto "example": "imap.gmail.com" // placeholder/esempio (opzionale) }, { "name": "EMAIL_PASSWORD", "label": "Password / app password", "description": "Password o app-password del provider", "required": true, "secret": true, "default": "" // valore di default se non obbligatorio (opzionale) } ] ``` | Campo | Obbligatorio | Descrizione | |-------|-------------|-------------| | `name` | ✅ | Nome della variabile d'ambiente (UPPER_SNAKE_CASE) | | `label` | ✅ | Etichetta breve per la UI | | `description` | ✅ | Testo di aiuto | | `required` | ✅ | Se `true`, l'host obbliga l'utente a fornire un valore | | `secret` | consigliato | Se `true`, valore sensibile (mascherato, non loggato) | | `default` | opzionale | Valore usato se non fornito (solo per non obbligatorie) | | `example` | opzionale | Placeholder di esempio per la UI | ## Sintassi placeholder (unificata) Ogni valore che skald deve riempire a runtime con un dato fornito dall'utente usa **uno di due token**, ovunque compaia (URL, `mcp_config.env`, `verify.command`): | Token | Significato | Esempio | |-------|-------------|---------| | `{ENV:NAME}` | Variabile non sensibile (hostname, porta, username…) | `{ENV:EMAIL_IMAP_HOST}` | | `{SECRET:NAME}` | Variabile sensibile (password, API key, token) | `{SECRET:EMAIL_PASSWORD}` | `NAME` è il nome dichiarato nell'array `env[]` (campo `name`). skald raccoglie i valori tramite un form (maschera i `{SECRET:}`), li inietta come environment nel processo del server MCP / verify, e sostituisce i token nel manifest. Regole: - I token non riconosciuti (es. `{secrets}/…`, legacy `{key}`, `{env:NAME}`) sono **deprecati**: skald non li sostituisce e il manifest va aggiornato. - `{SECRET:}` è riservato alla chiave primaria quando `auth.type = "api_key"` (es. Tavily: `{SECRET:tavilyApiKey}`). skald tratta quel valore anche come API key per il routing bearer/header. - Un token `{ENV:X}` o `{SECRET:X}` la cui `X` non è nell'`env[]` del manifest viene sostituito con stringa vuota (l'host non può indovinarlo). ### Deprecations | Token | Stato | Sostituzione | |-------|-------|--------------| | `{key}` | ❌ deprecato | `{SECRET:}` | | `{env:NAME}` | ❌ deprecato | `{ENV:NAME}` | | `{secrets}/…` | ❌ deprecato | Il connector deve dichiarare il path come `{ENV:…}` (la cartella `secrets/` è rimossa dal modello) | ## Campo verify (test prima del salvataggio) Dichiara un comando shell che skald esegue **dopo** che l'utente ha compilato il form e **prima** di persistere l'attivazione. Serve a verificare che le credenziali appena inserite funzionino davvero. ```json "verify": { "command": "python3 verify.py", "timeout_secs": 20 } ``` | Campo | Obbligatorio | Descrizione | |-------|-------------|-------------| | `command` | ✅ | Comando shell. Gira nello stesso sandbox del server: container `skald-{userid}` per `mcp_local` user, host per `mcp_remote` global. Le env/secret dichiarati sono iniettate | | `timeout_secs` | opzionale | Default 15. skald killa il processo allo scadere | ### Convenzione output Il comando deve stampare **un singolo oggetto JSON su stdout** e nient'altro: ```json {"ok": true, "message": "IMAP and SMTP authentication successful", "details": {"imap": "...", "smtp": "..."}} {"ok": false, "message": "IMAP login failed: INVALID_CREDENTIALS"} ``` | Campo | Tipo | Descrizione | |-------|------|-------------| | `ok` | bool | `true` = test passato | | `message` | string | Messaggio mostrato all'utente (mai loggare secret qui dentro) | | `details` | object | Opzionale, dettagli strutturati mostrati in `
` |

Exit code: 0 su successo, ≠ 0 su fallimento (skald usa l'exit code come
fallback se il parse JSON fallisce). **Mai stampare credenziali** nel
`message`/`details`.

### Dove mettere lo script

Se `command` referenzia un file (es. `verify.py`), il file va:
1. Aggiunto all'array `files[]` in `connectors.json` (con `sha256` e `size`)
2. Salvato nella cartella del connector (`/verify.py`)

skald lo scarica, ne verifica lo SHA-256 contro l'indice, e lo rende disponibile
nello stesso path del server principale (container per i `mcp_local`, dir
`./scripts//` sull'host per i `mcp_remote`).

### Senza verify

Se `verify` manca, skald **non esegue nessun test** — l'attivazione è diretta
come oggi. Il connector va in `auth_state='ready'` senza verifica. Per i
`mcp_remote` non c'è fallback handshake: l'autore del manifest decide se vuole
il test scrivendo `verify`.

## Convenzioni icone

- **Formato**: SVG per icone vettoriali (meglio per retina/zoom), PNG per raster
- **Nome**: `icon_sm.{svg|png}` (small, ~48×48px), `icon_lg.{svg|png}` (large, ~96×96px)
- **Path**: relativo alla cartella del connector
- **Nell'indice** la path è `{folder}/{filename}` (es. `gmail/icon_sm.svg`)

## Integrità file (sha256)

- Gli SHA-256 sono generati **automaticamente** da `scripts/compile.py` a partire
  dai file fisici presenti in ogni cartella — mai manuali, mai stale.
- L'unico file firmato (in futuro) sarà `connectors.json` (l'indice).
- `fragment.json` e `index.json` non hanno hash — sono solo input di compilazione.

## Struttura directory

```
connectors/
├── index.json              ← lista ordinata degli id dei connector (input per compile.py)
├── connectors.json         ← INDICE COMPILATO (generato da compile.py, non editare)
├── compile.py              ← genera connectors.json (lanciato da scripts/compile.py)
├── index.html              ← Catalogo UI (legge connectors.json via fetch)
├── oauth/
│   └── show.html           ← OAuth callback receiver
├── gmail/                   ← Un connector per cartella
│   ├── fragment.json        ← Frammento dell'indice (id, name, type, ..., SENZA files[])
│   ├── connector.json       ← Configurazione tecnica (mcp_config, auth.deliver, ...)
│   ├── gmail_mcp_server.py  ← Script MCP
│   ├── gmail_oauth_setup.py ← Script setup OAuth
│   ├── requirements.txt     ← Dipendenze Python
│   ├── icon_sm.svg          ← Icona piccola (48×48)
│   └── icon_lg.svg          ← Icona grande (es. 96×96)
├── email/
│   ├── fragment.json
│   ├── connector.json
│   ├── email_mcp_server.py
│   ├── verify.py
│   ├── requirements.txt
│   ├── icon_sm.svg
│   └── icon_lg.svg
└── ...
```

## Workflow locale

1. **Aggiungere un nuovo connector**:
   - Crea cartella `connectors//`
   - Crea `fragment.json` (id, name, type, scope, icone, auth, tools, ...)
   - Crea `connector.json` (config tecnica: mcp_config, launch_command, ...)
   - Aggiungi script MCP, icone, verify.py, requirements.txt
   - Aggiungi l'id a `connectors/index.json`
   - Lancia `python3 scripts/compile.py`

2. **Modificare un connector esistente**:
   - Modifica i file nella cartella del connector
   - **Non toccare** `connectors.json` — viene rigenerato
   - Lancia `python3 scripts/compile.py`

3. **Prima del deploy**:
   ```bash
   python3 scripts/compile.py           # rigenera connectors.json con SHA-256 fresh
   python3 scripts/compile.py --verify  # (opzionale) verifica che sia aggiornato
   ```

4. **Deploy sul server**:
   ```bash
   ssh dguiducci@skald-home-server /home/dguiducci/marketplace_deploy.sh
   ```
   oppure con MCP SSH:
   ```bash
   # via mcp__ssh__exec alias "skald-home-server"
   /home/dguiducci/marketplace_deploy.sh
   ```
   (Il deploy script fa `git pull` + `cp -r connectors/* /var/www/connectors.skaldagent.net/`)

5. Verifica su `https://connectors.skaldagent.net/`

## Fragment.json

Vedi [connector.manifest_guide.md](docs/connector.manifest_guide.md) per i dettagli.

`fragment.json` contiene tutti i campi dell'entry di `connectors.json` **tranne** `files[]`.
Questi sono i campi obbligatori:

| Campo | Obbligatorio | Descrizione |
|-------|-------------|-------------|
| `id` | ✅ | Identificatore unico (match con folder name) |
| `name` | ✅ | Nome visualizzato |
| `type` | ✅ | `mcp_remote` o `mcp_local` |
| `scope` | ✅ | `global` o `user` |
| `icon_small` | ✅ | Path relativo dalla root del marketplace |
| `icon_large` | ✅ | Path relativo dalla root del marketplace |
| `user_description` | ✅ | Descrizione breve per la UI |
| `requires` | ✅ | Array di enum requisiti |
| `tags` | ✅ | Array di tag per filtraggio |
| `folder` | ✅ | Nome della cartella del connector (match con `id`) |
| `version` | ✅ | Intero per-connector, +1 a ogni modifica dei file |
| `version_string` | ✅ | Semver (solo display) |
| `version_release_date` | ✅ | Data ISO 8601 (solo display) |
| `tools` | ✅ | Array di `{name, display_name}` per friendly names UI |
| `auth` | opzionale | Configurazione autenticazione (se diversa da `"none"`) |

Nota: l'array `files[]` (con SHA-256 e size) viene aggiunto **automaticamente**
da `compile.py` scansionando i file presenti nella cartella — non va mai scritto
a mano.

## Deploy su server remoto

Il server remoto è:

- **Host**: skald-home-server (192.168.1.100 / 145.40.169.107)
- **User**: dguiducci
- **Path**: `/var/www/connectors.skaldagent.net/`
- **Proprietario**: `caddy:caddy`
- **Sudo**: richiesto per scrivere in `/var/www/`

## Connector attuali

| ID | Nome | Tipo | Scope | Auth | Verify |
|----|------|------|-------|------|--------|
| `context7` | Context7 | `mcp_remote` | `global` | none | `verify.py` (MCP initialize probe) |
| `gmaps` | Google Maps | `mcp_local` | `global` | api_key (env: `GOOGLE_MAPS_API_KEY`) | `verify.py` (Geocoding API probe) |
| `exa` | Exa | `mcp_remote` | `global` | api_key (`{SECRET:exaApiKey}` in URL — optional, free tier) | `verify.py` (MCP initialize probe) |
| `tavily` | Tavily | `mcp_remote` | `global` | api_key (`{SECRET:tavilyApiKey}` in URL) | `verify.py` (HTTP probe `/search`) |
| `serpapi-flights` | SerpAPI Flights | `mcp_remote` | `global` | api_key (`{SECRET:serpapiApiKey}` in URL) | `verify.py` (MCP initialize probe) |
| `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 | — |
| `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
(script + JSON output); `gmail` aspetta la Fase 2 (OAuth via loopback listener).
Un connector senza `verify` viene attivato senza test — vedi § Senza verify.