Files
skald-connectors/SKALD.md
T
Daniele fa8bbcb808 add playwright connector (v1 / 1.0.0) + commit pending docs reorganization
- new global mcp_local connector wrapping @playwright/mcp@0.0.79:
  index.js argv wrapper (--headless --isolated --no-sandbox) importing
  the package cli.js, postinstall downloads chromium-only browser,
  verify.js headless-launch probe, 24 tools with display names
- also carries the pending docs work: changelog extracted to
  CHANGELOG.md, manifest guide, SKALD.md/CLAUDE.md updates
2026-08-19 17:37:13 +01:00

23 KiB
Raw Blame History

Skald Connectors Marketplace

What it is

The marketplace is the catalog of tested connectors for Skald. Each connector is an adapter that lets Skald interface with an external service (API, email, calendar, search, messaging, etc.).

Two types of connectors:

  • mcp_remote — an already-hosted MCP server, reachable via URL (e.g. Tavily).
  • mcp_local — a Python/Node script to run client-side (e.g. Gmail, Google Calendar).

Local references

Remote: https://git.skaldagent.net/dguiducci/skald-connectors.git Live: https://connectors.skaldagent.net/ OAuth callback: https://connectors.skaldagent.net/oauth/show.html

Deploy

The main branch is the release branch. Only production-ready code ends up here. Development and alpha versions will live on separate branches in the future.

Quick deploy with MCP SSH:

mcp__ssh__exec alias=skald-home-server command="/home/dguiducci/marketplace_deploy.sh"

Or via classic SSH:

ssh dguiducci@skald-home-server /home/dguiducci/marketplace_deploy.sh

The script on the server does:

  1. git pull in /home/dguiducci/repos/skald-connectors/
  2. cp -r connectors/* into /var/www/connectors.skaldagent.net/

The /var/www/connectors.skaldagent.net/ directory is owned by dguiducci, so no sudo is needed for the copy.

Before deploying, remember to regenerate the index:

python3 scripts/compile.py

Friendly tool names (2026-07-21)

Every MCP tool must expose a friendly name for the Skald UI. Two ways, in order of preference:

  1. Via the MCP script (preferred) — add "title": "Friendly Name" in the definition of each tool inside tools/list. Works for all local scripts (Python/Node) that we control.
  2. Via the manifest (fallback) — add "tools": [{"name": "...", "display_name": "..."}] in connector.json and in connectors.json. Used only for remote connectors or external packages (e.g. npx -y firecrawl-mcp).

Resolution order (Skald tries them in this order):

  1. tools[].display_name from the manifest
  2. title from the MCP server's tools/list
  3. Automatic prettify of the raw name (send_message → "Send Message")

Current status (2026-07-21): all 13 marketplace connectors have title in the script or tools[] in the manifest.

Directory structure

connectors/
├── index.json              ← ordered list of connector ids (input for compile.py)
├── connectors.json         ← COMPILED INDEX (generated by compile.py, do not edit)
├── compile.py              ← generates connectors.json (launched by scripts/compile.py)
├── index.html              ← Catalog UI (reads connectors.json via fetch)
├── oauth/
│   └── show.html           ← OAuth callback receiver
├── gmail/                   ← one connector per folder
│   ├── fragment.json        ← index fragment (id, name, type, ..., WITHOUT files[])
│   ├── connector.json       ← technical configuration (mcp_config, auth.deliver, ...)
│   ├── gmail_mcp_server.py  ← MCP script
│   ├── gmail_oauth_setup.py ← OAuth setup script
│   ├── requirements.txt     ← Python dependencies
│   ├── icon_sm.svg          ← small icon (48×48)
│   └── icon_lg.svg          ← large icon (e.g. 96×96)
├── email/
│   ├── fragment.json
│   ├── connector.json
│   ├── email_mcp_server.py
│   ├── verify.py
│   ├── requirements.txt
│   ├── icon_sm.svg
│   └── icon_lg.svg
└── ...

Schema — connectors.json (root)

This is the single root of trust. It contains type, scope, and the sha256 of each connector's files. It has no hash of itself — in the future it may be digitally signed.

{
  "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": [
        {"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}
      ]
    }
  ]
}

Index fields

Field Required Description
id Unique identifier (kebab-case)
name Displayed name
type mcp_remote or mcp_local
scope global or user
icon_small Path relative to the marketplace root
icon_large Path relative to the marketplace root
user_description Short description for the UI
requires Array of requirement enums
tags Array of tags for filtering
folder Name of the connector folder
version Per-connector integer, +1 on every file change
version_string Semver (display only)
version_release_date ISO 8601 date YYYY-MM-DD (display only)
files Array of files with sha256 (NO self-hash)

Schema — connector.json (per folder)

Technical configuration for connector activation.

{
  "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"
}

connector.json fields

Field Required Description
id Unique identifier (matches the folder name)
name Displayed name
version Per-connector integer, +1 on every file change
version_string Semver (display only)
version_release_date ISO 8601 date YYYY-MM-DD (display only)
type mcp_remote or mcp_local
scope global or user
requires Array of requirement enums
tags Array of tags
auth Authentication configuration object
docs Array of multilingual documentation. llm_short_description is the field that ends up in the LLM's system prompt — it must describe WHAT the connector DOES, not list its tools (the LLM sees them after activate_tools). Example: "Weather — current conditions, 16-day forecast, and AQI data for any location."
icon_small Icon filename in the local folder
icon_large Icon filename in the local folder
launch_command mcp_local only Command to start the MCP server
transport mcp_local only stdio (default)
dependencies recommended Python/Node dependencies (empty array if stdlib only)
env if requires includes ENV Environment variables the user must provide (schema for the UI) — see § The env field
setup_instructions recommended Steps to configure the connector
mcp_config mcp_local only Configuration for the MCP client
homepage optional Service URL

Reserved enums

type (connector type)

Value Description Examples
mcp_remote Hosted MCP server, reachable via URL Tavily, Weather
mcp_local Script to run locally Gmail, Google Calendar, WhatsApp
script Standalone script (non-MCP) (future)

scope (configuration scope)

Value Description Examples
global A single instance/config for the whole system Tavily, Weather, Google Trends
user Each user has their own instance/authentication Gmail, WhatsApp, Google Calendar

requires (prerequisites)

Value Description
API_KEY Requires an API key to configure
OAUTH Requires OAuth authentication (Google, etc.)
DOCKER Requires Docker Engine
NODE Requires Node.js runtime
PYTHON Requires Python 3
SECRETS_DIR Deprecated — the secrets/ folder is removed from the model; connectors must use ENV/SECRET (see § Placeholder syntax)
ENV Requires environment variables (declared in the manifest env field)

The auth field

Structure describing how the connector handles authentication:

// 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 is ONLY a slug (Skald resolves endpoints + client secrets)
{"type": "oauth2", "provider": "google", "scopes": ["...", "..."]}

// OAuth2 with deliver (Skald injects the authorized_user JSON via env var)
{"type": "oauth2", "provider": "google", "scopes": ["..."],
 "deliver": {"as": "env", "format": "google_authorized_user", "env": "GMAIL_CREDS_JSON"}}

// OAuth2 with file-based deliver (legacy)
{"type": "oauth2", "provider": "google", "scopes": ["..."],
 "deliver": {"as": "file", "format": "google_authorized_user", "path": "{secrets}/gmail_creds.json"}}

// Password / app-password provided via environment variables
{"type": "password", "delivery": "env"}

// No authentication
{"type": "none"}

The deliver field (OAuth2 only)

Declares how Skald delivers the obtained OAuth credential to the MCP server process.

Field Required Description
as "file" (on disk) or "env" (environment variable)
format Name of the serialization — e.g. "google_authorized_user" (Google JSON that from_authorized_user_file reads), "refresh_token", "access_token"
path as=file only Path with the {secrets} placeholder (Skald expands it to a per-user dir at runtime). MUST match the path in mcp_config.env.
env as=env only Name of the environment variable into which Skald injects the entire authorized_user JSON. Must not be declared in mcp_config.env — Skald injects it at runtime.

The feed NEVER contains: client_id, client_secret, endpoint URL, redirect_uri. These are resolved on the Skald side from the provider name.

The env field (environment variables)

Used when requires includes ENV. It is an array declaring the environment variables the user must provide to make the connector work; no credential lands on disk nor in secrets/ — the host collects the values, enforces filling in the required fields, and injects them as environment into the MCP server process at launch. The server reads them from os.environ.

"env": [
  {
    "name": "EMAIL_IMAP_HOST",   // name of the environment variable
    "label": "IMAP host",         // label for the UI
    "description": "IMAP server hostname (e.g. imap.gmail.com)",
    "required": true,             // if true, the host must enforce this field
    "secret": false,              // if true, the UI masks it and treats it as a secret
    "example": "imap.gmail.com"   // placeholder/example (optional)
  },
  {
    "name": "EMAIL_PASSWORD",
    "label": "Password / app password",
    "description": "Password or app-password of the provider",
    "required": true,
    "secret": true,
    "default": ""                 // default value if not required (optional)
  }
]
Field Required Description
name Name of the environment variable (UPPER_SNAKE_CASE)
label Short label for the UI
description Help text
required If true, the host forces the user to provide a value
secret recommended If true, sensitive value (masked, not logged)
default optional Value used when not provided (non-required fields only)
example optional Example placeholder for the UI

Placeholder syntax (unified)

Every value skald must fill at runtime with user-provided data uses one of two tokens, wherever it appears (URL, mcp_config.env, verify.command):

Token Meaning Example
{ENV:NAME} Non-sensitive variable (hostname, port, username…) {ENV:EMAIL_IMAP_HOST}
{SECRET:NAME} Sensitive variable (password, API key, token) {SECRET:EMAIL_PASSWORD}

NAME is the name declared in the env[] array (the name field). skald collects the values via a form (masking {SECRET:} fields), injects them as environment into the MCP server / verify process, and substitutes the tokens in the manifest.

Rules:

  • Unrecognized tokens (e.g. {secrets}/…, legacy {key}, {env:NAME}) are deprecated: skald does not substitute them and the manifest must be updated.
  • {SECRET:<auth.param>} is reserved for the primary key when auth.type = "api_key" (e.g. Tavily: {SECRET:tavilyApiKey}). skald also treats that value as the API key for bearer/header routing.
  • A {ENV:X} or {SECRET:X} token whose X is not in the manifest's env[] is substituted with an empty string (the host cannot guess it).

Deprecations

Token Status Replacement
{key} deprecated {SECRET:<auth.param>}
{env:NAME} deprecated {ENV:NAME}
{secrets}/… deprecated The connector must declare the path as {ENV:…} (the secrets/ folder is removed from the model)

The verify field (test before save)

Declares a shell command that skald runs after the user has filled in the form and before persisting the activation. It checks that the credentials just entered actually work.

"verify": {
  "command": "python3 verify.py",
  "timeout_secs": 20
}
Field Required Description
command Shell command. Runs in the same sandbox as the server: container skald-{userid} for mcp_local user, host for mcp_remote global. The declared env/secrets are injected
timeout_secs optional Default 15. skald kills the process at expiry

Output convention

The command must print a single JSON object on stdout and nothing else:

{"ok": true,  "message": "IMAP and SMTP authentication successful", "details": {"imap": "...", "smtp": "..."}}
{"ok": false, "message": "IMAP login failed: INVALID_CREDENTIALS"}
Field Type Description
ok bool true = test passed
message string Message shown to the user (never log secrets inside)
details object Optional, structured details shown in <pre>

Exit code: 0 on success, ≠ 0 on failure (skald uses the exit code as a fallback if the JSON parse fails). Never print credentials in the message/details.

Where to put the script

If command references a file (e.g. verify.py), the file must:

  1. Be added to the files[] array in connectors.json (with sha256 and size)
  2. Be saved in the connector folder (<id>/verify.py)

skald downloads it, verifies its SHA-256 against the index, and makes it available at the same path as the main server (container for mcp_local ones, ./scripts/<id>/ dir on the host for mcp_remote ones).

Without verify

If verify is absent, skald runs no test — activation is direct, as today. The connector goes to auth_state='ready' without verification. For mcp_remote there is no handshake fallback: the manifest author decides whether the test is needed by writing verify.

Icon conventions

  • Format: SVG for vector icons (better for retina/zoom), PNG for raster
  • Name: icon_sm.{svg|png} (small, ~48×48px), icon_lg.{svg|png} (large, ~96×96px)
  • Path: relative to the connector folder
  • In the index the path is {folder}/{filename} (e.g. gmail/icon_sm.svg)

File integrity (sha256)

  • SHA-256 hashes are generated automatically by scripts/compile.py from the physical files present in each folder — never manual, never stale.
  • The only file signed (in the future) will be connectors.json (the index).
  • fragment.json and index.json have no hash — they are just compilation inputs.

Local workflow

  1. Adding a new connector:

    • Create the folder connectors/<id>/
    • Create fragment.json (id, name, type, scope, icons, auth, tools, ...)
    • Create connector.json (technical config: mcp_config, launch_command, ...)
    • Add the MCP script, icons, verify.py, requirements.txt
    • Add the id to connectors/index.json
    • Run python3 scripts/compile.py
  2. Modifying an existing connector:

    • Edit the files in the connector folder
    • Do not touch connectors.json — it gets regenerated
    • Run python3 scripts/compile.py
  3. Before deploying:

    python3 scripts/compile.py           # regenerates connectors.json with fresh SHA-256s
    python3 scripts/compile.py --verify  # (optional) verifies that it is up to date
    
  4. Deploy to the server:

    ssh dguiducci@skald-home-server /home/dguiducci/marketplace_deploy.sh
    

    or with MCP SSH:

    # via mcp__ssh__exec alias "skald-home-server"
    /home/dguiducci/marketplace_deploy.sh
    

    (The deploy script does git pull + cp -r connectors/* /var/www/connectors.skaldagent.net/)

  5. Verify on https://connectors.skaldagent.net/

Fragment.json

See docs/connector.manifest_guide.md for the complete creation guide.

fragment.json contains all the fields of a connectors.json entry except files[]. These are the required fields:

Field Required Description
id Unique identifier (matches the folder name)
name Displayed name
type mcp_remote or mcp_local
scope global or user
icon_small Path relative to the marketplace root
icon_large Path relative to the marketplace root
user_description Short description for the UI
requires Array of requirement enums
tags Array of tags for filtering
folder Name of the connector folder (matches id)
version Per-connector integer, +1 on every file change
version_string Semver (display only)
version_release_date ISO 8601 date (display only)
tools Array of {name, display_name} for friendly UI names
auth optional Authentication configuration (if other than "none")

Note: the files[] array (with SHA-256 and size) is added automatically by compile.py by scanning the files present in the folder — it must never be written by hand.

Deploy to remote server

The remote server is:

  • Host: skald-home-server (192.168.1.100 / 145.40.169.107)
  • User: dguiducci
  • Path: /var/www/connectors.skaldagent.net/
  • Owner: caddy:caddy
  • Sudo: required to write in /var/www/

Current connectors

ID Name Type 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) Phase 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
playwright Playwright mcp_local global none verify.js (headless Chromium launch probe)

verify-before-save status in skald: exa, drive, email, tavily, and gcal have a complete verify (script + JSON output); gmail awaits Phase 2 (OAuth via loopback listener). A connector without verify is activated without any test — see § Without verify.