Add Email (IMAP/SMTP) and SSH Remote Access connectors

- CLAUDE.md: developer guide for the marketplace repo
- email/: generic IMAP+SMTP connector (stdlib only, config via env)
  - env[] array, auth.type: password, verify.py (IMAP+SMTP probe)
- ssh/: SSH Remote Access connector ported from Skald, modified
  - ALIASES_FILE → ~/.ssh_aliases.json (was ./secrets/...)
  - optional env[] (TTL/timeout tunables)
  - auth.type: none (per-alias auth at runtime via elicitation)
- tavily/: restructured with env[] (tavilyApiKey), verify.py, auth block
- connectors.json: added email, ssh, and tavily verify.py entries
- SKALD.md: documented env[], verify, password auth, SSH connector
This commit is contained in:
2026-07-16 22:29:33 +01:00
parent dedd09d7c7
commit 1caba6946c
15 changed files with 3420 additions and 8 deletions
+142 -6
View File
@@ -2,6 +2,8 @@
_Updated: 2026-07-16_
**Remote**: `https://git.skaldagent.net/dguiducci/skald-connectors.git` (branch: `main`)
## 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.).
@@ -149,7 +151,8 @@ Configurazione tecnica per l'attivazione del connector.
| `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 |
| `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 |
@@ -180,7 +183,8 @@ Configurazione tecnica per l'attivazione del connector.
| `DOCKER` | Richiede Docker Engine |
| `NODE` | Richiede Node.js runtime |
| `PYTHON` | Richiede Python 3 |
| `SECRETS_DIR` | Richiede file di credenziali in `secrets/` |
| `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
@@ -196,10 +200,137 @@ Struttura che descrive come il connector gestisce l'autenticazione:
// OAuth2
{"type": "oauth2", "provider": "google", "scopes": ["...", "..."]}
// Password / app-password fornita via variabili d'ambiente
{"type": "password", "delivery": "env"}
// Nessuna autenticazione
{"type": "none"}
```
## 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:<auth.param>}` è 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:<auth.param>}` |
| `{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 `<pre>` |
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 (`<id>/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/<id>/` 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
@@ -256,7 +387,12 @@ sudo find /var/www/connectors.skaldagent.net/ -type f -exec chmod 644 {} \;
## Connector attuali
| ID | Nome | Tipo | Scope |
|----|------|------|-------|
| `tavily` | Tavily | `mcp_remote` | `global` |
| `gmail` | Gmail | `mcp_local` | `user` |
| ID | Nome | Tipo | Scope | Auth | Verify |
|----|------|------|-------|------|--------|
| `tavily` | Tavily | `mcp_remote` | `global` | api_key (`{SECRET:tavilyApiKey}` in URL) | `verify.py` (HTTP probe `/search`) |
| `gmail` | Gmail | `mcp_local` | `user` | oauth2 (Google) | ⏳ Fase 2 — il flusso OAuth non è ancora cablato in skald |
| `email` | Email (IMAP/SMTP) | `mcp_local` | `user` | password (env) | `verify.py` (IMAP+SMTP probe) |
| `ssh` | SSH Remote Access | `mcp_local` | `user` | none (auth runtime per-alias) | — (nessun setup credential) |
**Stato del verify-before-save in skald**: `email` e `tavily` 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.