auth.deliver changes from as:file to as:env: - Gmail: GMAIL_CREDS_JSON env var (Google authorized_user JSON) - Gcal: GCAL_CREDS_JSON env var mcp_config.env removed entirely — Skald injects the env var at runtime, no path on disk needed. Server scripts updated: - Check GMAIL_CREDS_JSON / GCAL_CREDS_JSON env var first - Use Credentials.from_authorized_user_info() instead of from_authorized_user_file() - Fall back to file-based loading for standalone/legacy use - _persist_creds only writes to disk when _creds_path is set gcal/verify.py: support GCAL_CREDS_JSON env var with shared _check_api() Docs (SKALD.md): updated examples, field table, connector table.
450 lines
18 KiB
Markdown
450 lines
18 KiB
Markdown
# Skald Connectors Marketplace
|
||
|
||
_Updated: 2026-07-17_
|
||
|
||
**Remote**: `https://git.skaldagent.net/dguiducci/skald-connectors.git` (branch: `main`)
|
||
**Live**: `https://connectors.skaldagent.net/`
|
||
**OAuth callback**: `https://connectors.skaldagent.net/oauth/show.html`
|
||
|
||
## 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",
|
||
"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 |
|
||
| `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.0.0",
|
||
"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` | ✅ | SemVer |
|
||
| `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.
|