Files
skald-connectors/SKALD.md
T
dguiducci 7039816b73 Add versioning schema: version (int), version_string, version_release_date to all 10 connectors
- version: intero per-connector (parte da 1)
- version_string: semver ereditato (display only)
- version_release_date: ISO 8601 (display only)
- Campi identici in index (connectors.json) e manifest (connector.json)
- Aggiunto connector.json alle files[] di gmail, gcal, email, ssh, tavily
- Aggiornata documentazione in SKALD.md
2026-07-19 10:23:22 +01:00

478 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Skald Connectors Marketplace
_Updated: 2026-07-17_
**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.
Per deployare l'ultima versione del marketplace sul server:
```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.
## 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).
## Struttura directory
```
connectors/
├── connectors.json ← INDICE (radice di fiducia unica)
├── index.html ← Catalogo UI (legge connectors.json via fetch)
├── oauth/
│ └── show.html ← OAuth callback receiver (client-side, no backend)
├── gmail/ ← Un connector per cartella
│ ├── 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. preview)
├── email/
│ ├── connector.json
│ ├── email_mcp_server.py
│ ├── verify.py
│ ├── requirements.txt
│ ├── icon_sm.svg
│ └── icon_lg.svg
├── ssh/
│ ├── connector.json
│ ├── ssh_mcp_server.py
│ ├── requirements.txt
│ ├── icon_sm.svg
│ └── icon_lg.svg
└── tavily/
├── connector.json
├── verify.py
├── icon_sm.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": "One-line description for LLM context..."
}
],
"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 |
| `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:<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
- **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 hash sha256 sono **solo in `connectors.json`** (l'indice)
- `connector.json` **non contiene hash di sé stesso** — è il file manifesto
- L'indice è l'unica radice di fiducia; in futuro si può firmare digitalmente solo l'indice
- Sui file locali, per calcolare/aggiornare gli hash:
```bash
python3 -c "import hashlib; print(hashlib.sha256(open('file.py','rb').read()).hexdigest())"
```
## Workflow locale
1. Lavora su file nella cartella `connectors/`
2. Modifica `connectors.json`, `connector.json`, script, icone
3. **Prima del deploy** aggiorna gli sha256 in `connectors.json`:
```bash
python3 scripts/update_hashes.py
```
4. Fai l'upload sul server con rsync:
```bash
rsync -avz --delete connectors/ dguiducci@skald-server:/var/www/connectors.skaldagent.net/
```
5. Verifica su `https://connectors.skaldagent.net/`
## 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/`
Comando di deploy (da eseguire con sudo o con rsync):
```bash
# Sync se il server permette rsync via SSH
rsync -avz --delete ./connectors/ dguiducci@skald-server:/var/www/connectors.skaldagent.net/
```
Dopo il deploy, verificare i permessi:
```bash
sudo chown -R caddy:caddy /var/www/connectors.skaldagent.net/
sudo find /var/www/connectors.skaldagent.net/ -type f -exec chmod 644 {} \;
```
## Connector attuali
| 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) + 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) |
| `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`, `tavily`, e `gcal` hanno `verify` completo
(script + JSON output); `gmail` aspetta la Fase 2 (OAuth via loopback listener).
(script + JSON output); `gmail` aspetta la Fase 2 (OAuth via loopback listener).
Un connector senza `verify` viene attivato senza test — vedi § Senza verify.