Both connectors declared `mcp_config: {command: "npx", args: ["-y", "<pkg>"]}`
and shipped no code at all. Skald treats a mcp_local `args[0]` as the *name of
the file to run*, not as an argument: install stores `script_path = "<id>/-y"`
and global_enable launches `npx /…/connectors/<id>/-y`, a path that does not
exist. The server never answered `initialize`, so start_server failed — and
since global_enable still returns 200 with an `error` in the body, the UI showed
the connector as enabled while `render_mcp_list` (built from the live runtime)
left it out of the agent's system context entirely.
Ship a two-file wrapper instead: a package.json pinning the upstream package
(mcp-fetch-server@1.1.2, firecrawl-mcp@3.23.7) and an index.js importing it for
its side effect, so `node index.js` is a real local_script and
ensure_installed_host installs the deps beside it. No node_modules, no lockfile.
Also: drop the legacy `launch_command` / top-level `transport` / `dependencies`
fields; drop firecrawl's inert `mcp_config.env` placeholder (same case as gmaps
— tokens are only substituted in the URL); add firecrawl_developer_search to
firecrawl's tools[] and API_KEY to its requires; realign manifest and fragment
versions to 5 / 1.1.0 (they were 2 vs 4, so no Update badge could ever appear).
Verified end-to-end against the shipped files by reproducing Skald's own path:
`npm ci || npm install --omit=dev` then `node <abs>/index.js` — initialize,
tools/list and a real tools/call, JSON-RPC only on stdout.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
32 KiB
Skald Connectors Marketplace
2026-08-10 — http-fetch + firecrawl: fix npx (v5 / 1.1.0)
Entrambi i connector erano non funzionanti: dichiaravano mcp_config: {command: "npx", args: ["-y", "<package>"]} e non spedivano nessun file di codice (solo connector.json + icone).
npx -y <package>non è esprimibile in skald. Per untype: mcp_localskald trattaargs[0]come il nome del file da eseguire, non come un argomento: all'install calcolascript_path = "<id>/" + args[0]e azzeraargs_json(marketplace.rs::install), poiglobal_enable(api/mcp.rs) lo risolve in path assoluto e lancia<command> <abs>. Il comando reale diventavanpx /…/connectors/http-fetch/-y— un path inesistente, con-ye il nome del package spariti. Il processo non rispondeva mai ainitialize,start_serverfalliva.- Il fallimento era silenzioso:
global_enablerestituisce comunque HTTP 200 con un campoerrornel body, quindi la UI mostrava il connector come abilitato mentre il runtime non aveva nessun server. Erender_mcp_list(loop_adapters/system.rs) costruisce la tabella## MCP serversdamcp.tools(), cioè lo stato vivo del runtime, non dal DB → il connector risultava attivato e concesso all'utente ma assente dal system context. ⚠️ Questa combinazione (200 +errornel body) rende invisibile in UI qualsiasi connector che non parte: vale la pena farla emergere lato skald. - Fix — wrapper di due file per entrambi:
package.jsonche pinna il package upstream (mcp-fetch-server@1.1.2,firecrawl-mcp@3.23.7) eindex.jsche lo importa per side-effect (il modulo avvia il loop JSON-RPC su stdio all'import).mcp_configdiventa{command: "node", args: ["index.js"], transport: "stdio"}, cioè un verolocal_script:ensure_installed_hostfanpm ci --omit=dev || npm install --omit=devnella cartella del connector prima del lancio, esattamente come per whatsapp. Nessunnode_modulesspedito, nessun lockfile (come whatsapp). - Rimossi i campi legacy
launch_command,transporttop-level edependencies(dependenciesè solo per la card, come già visto su gmaps;transportva dentromcp_config). - firecrawl: rimosso
mcp_config.env: {"FIRECRAWL_API_KEY": "{SECRET:FIRECRAWL_API_KEY}"}— inerte, stesso caso di gmaps del 2026-08-10:apply_key_placeholdersostituisce i token solo nella URL, mai nei valori dienv. Funziona perché la form admin mandaenve quel payload sovrascriveentry.env_json. - firecrawl: aggiunto
firecrawl_developer_searchatools[](27 tool live contro i 26 dichiarati, verificato su 3.23.7);requires["NODE"]→["NODE", "API_KEY"]. - Versioni riallineate manifest↔fragment a
5/1.1.0/2026-08-10per entrambi: erano 2/1.0.1 (manifest) vs 4/1.0.3 (fragment), e skald preferisce il manifest — quindiinstalled_versionrestava 2 e il badge "Update available" non sarebbe mai comparso. - Requisito host: sono connector
scope: global, girano sull'host e non nel container.mcp-fetch-servervuole Node ≥18,firecrawl-mcpvuole Node ≥22. - Testati end-to-end riproducendo il path di skald (
npm ci || npm install+node <abs>/index.js):initialize,tools/liste unatools/callreale, stdout solo JSON-RPC, stderr pulito ✅ - Indice rigenerato con compile.py ✅
2026-08-10 — gmaps: fix dipendenze + verify (v6 / 1.1.0)
- Aggiunto
requirements.txt(googlemaps>=4.10.0) — era l'unico connector python senza. Le dipendenze erano dichiarate nel campodependenciesdel manifest, che skald usa solo per la card:ensure_installed_hostguarda esclusivamenterequirements.txt/package.json. Risultato:.pydepsvuoto e log pieno diNo module named 'googlemaps', con il server che rispondeva comunque atools/list(→connected — 6 tool(s)su un connector non funzionante). - Cablato il
verify(python3 verify.py, 20s):verify.pyera shippato ma il manifest non aveva il bloccoverify, e skald leggeverify_commandsolo da lì. Ora un'abilitazione con dipendenze rotte fallisce visibilmente invece di partire in silenzio. verify.pysi mette.pydepssusys.path: skald impostaPYTHONPATHsolo per il processo server (global_row_spec), mentre il verify gira comesh -c "python3 verify.py"senza. Senza questa riga il verify fallirebbe con "Missing dependency" anche su un connector installato correttamente, disabilitando la riga. ⚠️ Stesso rischio latente per ogni connector converifyche importa dipendenze non-stdlib (gcal in container): da controllare.- Rimosso
mcp_config.env: {"GOOGLE_MAPS_API_KEY": "{SECRET:…}"}(introdotto il 2026-07-23): inerte.apply_key_placeholdersostituisce i token{SECRET:}/{ENV:}solo nella URL, mai nei valori dienv. Funzionava perché la form admin mandaenve quel payload sovrascriveentry.env_json; a form vuota il processo avrebbe ricevuto la stringa letterale. ⚠️ La spec in questo file e in CLAUDE.md dice il contrario — o si corregge la spec, o si estende la sostituzione ai valori dienvlato skald. - Versioni riallineate manifest↔fragment a
6/1.1.0/2026-08-10: erano 2/1.0.1 vs 5/1.0.4, e skald preferisce il manifest (manifest.version.or(entry.version)) — quindiinstalled_versionrestava 2 e il badge "Update available" non sarebbe mai comparso. requires:ENV→API_KEY; messaggi d'errore del server ripuliti dai riferimenti asecrets/gmaps_api_key.txt(path deprecato).- Indice rigenerato con compile.py ✅
2026-08-07 — Nuovo connector: LinkedIn
- Aggiunto connector
linkedin(mcp_local, scope user): server.py + session.py + verify.py + icone PNG - Aggiunto a
connectors/index.json, indice rigenerato con compile.py (17 connector totali) .gitignoreaggiornato per ignorare.claude/- Deploy su connectors.skaldagent.net via alias
skaldserver(192.168.1.100, LAN — niente Tailscale)
2026-07-23 — gmaps: fix env var injection
- Aggiunto
mcp_config.envinconnector.jsonper iniettareGOOGLE_MAPS_API_KEYnel processo MCP - Il connector era dichiarato come
delivery: envma senzamcp_config.envSkald non sapeva passare la variabile al processo Python - Version bump: fragment 4→5, connector 1→2
- Indice rigenerato con compile.py ✅
2026-07-23 — Context7 icon update (PNG)
- Sostituite icone Context7 da SVG a PNG (icona nuova fornita dall'utente):
icon_sm.png— 48×48 (2.7 KB)icon_lg.png— 96×96 (4.5 KB)
- Vecchi SVG rimossi, riferimenti aggiornati in fragment.json e connector.json
- Version bump: fragment 3→4, connector 1→2
- Indice rigenerato con compile.py ✅
Reference locale
2026-07-23 — SerpAPI Flights icon update (PNG)
-
Sostituite icone SerpAPI Flights da SVG a PNG:
icon_sm.png— 48×48 (2.9 KB)icon_lg.png— 96×96 (6.8 KB)
-
Vecchi SVG rimossi, riferimenti aggiornati in fragment.json e connector.json
-
Version bump: fragment 4→5, connector 2→3
-
Indice rigenerato con compile.py ✅
-
docs/connector.manifest_guide.md — Guida ufficiale per generare connector corretti (copiata da
skald-circle/blueprint/)
Updated: 2026-07-23
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:
mcp__ssh__exec alias=skald-home-server command="/home/dguiducci/marketplace_deploy.sh"
Oppure via SSH classico:
ssh dguiducci@skald-home-server /home/dguiducci/marketplace_deploy.sh
Lo script sul server fa:
git pullin/home/dguiducci/repos/skald-connectors/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:
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:
- Via script MCP (preferito) — aggiungere
"title": "Friendly Name"nella definizione di ogni tool dentrotools/list. Funziona per tutti gli script locali (Python/Node) che controlliamo. - Via manifest (fallback) — aggiungere
"tools": [{"name": "...", "display_name": "..."}]inconnector.jsone inconnectors.json. Usato solo per connector remoti o package esterni (es.npx -y firecrawl-mcp).
Ordine di risoluzione (Skald li prova in quest'ordine):
tools[].display_namedal manifesttitledaltools/listdell'MCP server- 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.
{
"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:
// 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.
"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 quandoauth.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 cuiXnon è 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.
"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:
{"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:
- Aggiunto all'array
files[]inconnectors.json(consha256esize) - 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 SHA-256 sono generati automaticamente da
scripts/compile.pya partire dai file fisici presenti in ogni cartella — mai manuali, mai stale. - L'unico file firmato (in futuro) sarà
connectors.json(l'indice). fragment.jsoneindex.jsonnon 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
-
Aggiungere un nuovo connector:
- Crea cartella
connectors/<id>/ - 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
- Crea cartella
-
Modificare un connector esistente:
- Modifica i file nella cartella del connector
- Non toccare
connectors.json— viene rigenerato - Lancia
python3 scripts/compile.py
-
Prima del deploy:
python3 scripts/compile.py # rigenera connectors.json con SHA-256 fresh python3 scripts/compile.py --verify # (opzionale) verifica che sia aggiornato -
Deploy sul server:
ssh dguiducci@skald-home-server /home/dguiducci/marketplace_deploy.shoppure con MCP SSH:
# 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/) -
Verifica su
https://connectors.skaldagent.net/
Fragment.json
Vedi docs/connector.manifest_guide.md per la guida completa alla creazione.
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 |
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. |