CLAUDE.md and SKALD.md had drifted apart. CLAUDE.md still described a hand-maintained connectors.json (pre-compile.py), an rsync deploy, 5 connectors, and a files[] that excluded connector.json. CLAUDE.md is now the single working document, carrying every SKALD.md section — full schemas for connectors.json / fragment.json / connector.json, reserved enums, the auth/deliver/env/verify fields, placeholder syntax, friendly tool names, icon conventions, file integrity, local workflow and deploy — corrected against the actual repo state: 18 connectors, the scripts/compile.py pipeline, and connector.json included in the hashed files[]. It also names CONNECTOR_MANIFEST_GUIDE.md (repo root) as the authoritative connector-authoring spec.
29 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. It documents the repo layout, the schemas, and the build/deploy workflow — read it before changing any manifest. The authoritative spec for authoring a connector lives in this repo as CONNECTOR_MANIFEST_GUIDE.md (see § The spec).
What this is
The Skald Connectors Marketplace — the catalog of tested connectors for Skald. Each connector is an adapter that lets a Skald agent talk to an external service (API, email, calendar, search, messaging…).
It is not an application: there is no build system, package manager, or test suite. The repo is a set
of JSON manifests, static HTML, icons, and standalone MCP scripts (Python/Node) served as-is over HTTP.
The only tooling is scripts/compile.py, which regenerates the index.
- Remote git:
https://git.skaldagent.net/dguiducci/skald-connectors.git(branchmain) - Live site:
https://connectors.skaldagent.net/ - OAuth callback:
https://connectors.skaldagent.net/oauth/show.html
main is the release branch — only production-ready code lands here. Development/alpha versions will
live on separate branches in the future.
The spec
The marketplace spec ships inside this project: CONNECTOR_MANIFEST_GUIDE.md
at the repo root is the authoritative, step-by-step specification for producing a correct connector —
the two documents, the MCP-over-stdio server contract, friendly tool names, placement & risk vocabulary,
the auth.type variants (api_key / oauth2 / qr), dependency installation, verify-before-save,
versioning, and the new-connector checklist. Give this file to any agent that generates new connectors,
and update it whenever the connector format changes.
Consumers outside this repo (e.g. ~/projects/skald-circle) read that same file as the format reference.
docs/connector.manifest_guide.md is an earlier copy of the same guide;
it is the only one that documents the fragment.json + scripts/compile.py build pipeline, which
CONNECTOR_MANIFEST_GUIDE.md does not yet cover. Until the root guide absorbs that section, the compile
workflow is described below in § Architecture and § Local workflow.
Architecture
Two-tier manifest model with a single trust root, plus a compile step:
connectors/index.json— ordered list of connector ids. The input list forcompile.py.connectors/<id>/fragment.json— the connector's index entry withoutfiles[](id, name, type, scope, icons,user_description, requires, tags, version,tools[], auth…).connectors/<id>/connector.json— per-connector technical manifest used at activation time (launch command, transport,mcp_config,auth,env,verify,dependencies,docs). One folder per connector; the folder name matches the connectorid.connectors/connectors.json— the compiled index and single root of trust. Generated byscripts/compile.pyfromindex.json+ eachfragment.json+ a physical scan of every connector folder. It lists every connector with afiles[]array carrying thesha256+sizeof each shipped file. Never edit it by hand. It carries no hash of itself — in the future it may be digitally signed.connectors/index.html— catalog UI. Fetches/connectors.jsonat an absolute path and links to/<folder>/, so it only works when served from the site root (not opened as afile://).connectors/oauth/show.html— OAuth callback receiver.
connectors/
├── index.json ← ordered list of connector ids (input for compile.py)
├── connectors.json ← COMPILED INDEX (generated by compile.py, do not edit)
├── 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
│ ├── requirements.txt ← Python dependencies
│ ├── icon_sm.svg ← small icon (~48×48)
│ └── icon_lg.svg ← large icon (~96×96)
├── email/
│ ├── fragment.json
│ ├── connector.json
│ ├── email_mcp_server.py
│ ├── verify.py
│ ├── icon_sm.svg
│ └── icon_lg.svg
└── …
Schema — connectors.json (compiled root index)
{
"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": "connector.json", "sha256": "…", "size": 1204},
{"path": "gmail_mcp_server.py", "sha256": "…", "size": 46772},
{"path": "icon_lg.svg", "sha256": "…", "size": 254},
{"path": "icon_sm.svg", "sha256": "…", "size": 251},
{"path": "requirements.txt", "sha256": "…", "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 {path, sha256, size} — added automatically by compile.py, NO self-hash |
Schema — fragment.json (per folder)
fragment.json contains all the fields of a connectors.json entry except files[]:
| 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") |
The files[] array is added automatically by compile.py from the files present in the folder — it
must never be written by hand.
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"
],
"docs": [
{
"lang": "en",
"description": "Full description for human users…",
"llm_short_description": "Gmail — read, send, label, and search email. 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 (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 the connector needs user-supplied config | Environment variables the user must provide (schema for the UI) — see § The env field |
verify |
recommended | Test-before-save command — see § The verify field |
setup_instructions |
recommended | Steps to configure the connector |
mcp_config |
✅ | Configuration for the MCP client (command/args for mcp_local, url/transport for mcp_remote) |
homepage |
optional | Service URL |
Reserved enums
type (connector type)
| Value | Description | Examples |
|---|---|---|
mcp_remote |
Hosted MCP server, reachable via URL | Tavily, Exa |
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 |
ENV |
Requires environment variables (declared in the manifest env field) |
SECRETS_DIR |
❌ Deprecated — the secrets/ folder is removed from the model; connectors must use ENV/SECRET (see § Placeholder syntax) |
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"}
// API key delivered as an environment variable
{"type": "api_key", "delivery": "env", "param": "GOOGLE_MAPS_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"}
// QR-code pairing at runtime (WhatsApp)
{"type": "qr"}
// 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, the endpoint URL, or redirect_uri. These are
resolved on the Skald side from the provider name.
The env field (environment variables)
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 via a real form
(masking secret: true fields, enforcing required, using example as placeholder) and injects them
into the MCP server process environment at launch. The server reads them from os.environ.
"env": [
{
"name": "EMAIL_IMAP_HOST",
"label": "IMAP host",
"description": "IMAP server hostname (e.g. imap.gmail.com)",
"required": true,
"secret": false,
"example": "imap.gmail.com"
},
{
"name": "EMAIL_PASSWORD",
"label": "Password / app password",
"description": "Password or app-password of the provider",
"required": true,
"secret": true,
"default": ""
}
]
| 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 |
The email connector is the reference example. tavily, gmaps, and linkedin also declare env[].
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 field declared in the env[] array. 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 (
{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 whenauth.type = "api_key"(e.g. Tavily:?tavilyApiKey={SECRET:tavilyApiKey}). skald also treats that value as the API key for bearer/header routing.- A
{ENV:X}or{SECRET:X}token whoseXis not in the manifest'senv[]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 | 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 fills in the form and before persisting the activation, to confirm 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 message/details.
Where to put the script
If command references a file (e.g. verify.py), the file must be saved in the connector folder
(<id>/verify.py); compile.py then picks it up into files[] with its SHA-256 and size. skald
downloads it, verifies the hash against the index, and makes it available at the same path as the main
server (container for mcp_local, ./scripts/<id>/ on the host for mcp_remote). A verify command
may also be fully inline (see firecrawl, which uses a node -e "…" one-liner).
Without verify
If verify is absent, skald runs no test — activation is direct and 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.
Friendly tool names
Every MCP tool must expose a friendly name for the Skald UI. Two ways, in order of preference:
- Via the MCP script (preferred) — add
"title": "Friendly Name"to each tool definition returned bytools/list. Works for all local scripts (Python/Node) that we control. - Via the manifest (fallback) — add
"tools": [{"name": "…", "display_name": "…"}]infragment.jsonand inconnector.json. Used only for remote connectors or external packages (e.g.npx -y firecrawl-mcp).
Resolution order used by Skald:
tools[].display_namefrom the manifesttitlefrom the MCP server'stools/list- Automatic prettify of the raw name (
send_message→ "Send Message")
As of 2026-07-21 all marketplace connectors carry title in the script or tools[] in the manifest.
MCP server conventions
The local MCP servers (connectors/gmail/gmail_mcp_server.py, connectors/email/email_mcp_server.py,
connectors/wikipedia/, connectors/weather/, connectors/gmaps/, …) are hand-rolled JSON-RPC 2.0
servers over stdio — no MCP SDK / FastMCP. Shared conventions, which new local connectors must mirror:
- stdout is reserved for JSON-RPC; all logging goes to stderr, and a lock guards stdout writes.
handle_requestimplements the full handshake:initialize→protocolVersion 2024-11-05+serverInfo, a silentnotifications/initialized,ping→{}, andtools/list→{"tools": TOOLS}(an object, not a bare array).- Notifications are never answered: any message without an
idreturnsNone. - Tools are declared as a
TOOLSmanifest list plus aTOOL_DISPATCHmap. - Where relevant, a background thread emits push notifications (e.g.
event/new_email).
Connector-specific notes:
- Gmail — Gmail API with OAuth. Skald delivers the credential as
deliver: env/google_authorized_userintoGMAIL_CREDS_JSON; the script also falls back toGMAIL_CREDS_PATHor./secrets/gmail_creds.jsonfor standalone use (thatsecrets/path is deprecated and gitignored). Push = History API polling.verifyis not yet wired — it awaits Phase 2 (OAuth via loopback listener). - Email — generic IMAP+SMTP, stdlib-only (no dependencies), configured entirely from
{ENV:}/{SECRET:}env vars, works with any provider. Push = IMAP IDLE with a 60s polling fallback; the request thread and the watcher thread each hold their own IMAP connection (imaplib is not thread-safe). Note: imaplib does not quote SEARCH arguments, so values with spaces must be wrapped via_q(). - Tavily —
mcp_remote/global; API key declared as anenv[]secret and templated into the URL as{SECRET:tavilyApiKey}. - SSH —
mcp_local/user; stores aliases in~/.ssh_aliases.json(auto-managed, 0600). Auth per-alias: key/agent (default) or elicited password. Sudo vianopasswdor elicited password (sudo -S). No setup-time credentials:auth.type: "none"with noverify. AllSSH_MCP_*env vars are optional with defaults.
Current connectors
Order as in connectors/index.json (18 connectors).
| ID | Name | Type | Scope | Auth | Verify |
|---|---|---|---|---|---|
gmail |
Gmail | mcp_local |
user |
oauth2 (Google) + deliver env/google_authorized_user (GMAIL_CREDS_JSON) |
⏳ Phase 2 — OAuth via loopback listener |
gcal |
Google Calendar | mcp_local |
user |
oauth2 (Google) + deliver env/… (GCAL_CREDS_JSON) |
verify.py (creds load + API probe) |
drive |
Google Drive | mcp_local |
user |
oauth2 (Google) + deliver env/… (DRIVE_CREDS_JSON) |
verify.py (creds load + Drive API probe) |
email |
Email (IMAP/SMTP) | mcp_local |
user |
password (env) | verify.py (IMAP+SMTP probe) |
exa |
Exa | mcp_remote |
global |
api_key ({SECRET:exaApiKey} in URL — optional, free tier) |
verify.py (MCP initialize probe) |
firecrawl |
Firecrawl | mcp_local |
global |
api_key (env) | inline node -e scrape probe |
http-fetch |
HTTP Fetch | mcp_local |
global |
none | — |
serpapi-flights |
SerpAPI Flights | mcp_remote |
global |
api_key ({SECRET:serpapiApiKey} in URL) |
verify.py (MCP initialize probe) |
ssh |
SSH Remote Access | mcp_local |
user |
none (auth at runtime, per-alias) | — |
tavily |
Tavily | mcp_remote |
global |
api_key ({SECRET:tavilyApiKey} in URL) |
verify.py (HTTP probe /search) |
weather |
Weather (Open-Meteo) | mcp_local |
global |
none | — |
whatsapp |
mcp_local |
user |
qr | — | |
wikipedia |
Wikipedia | mcp_local |
global |
none | — |
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) |
google-trends |
Google Trends | mcp_local |
global |
none | verify.py (trendspyg import probe) |
linkedin |
mcp_local |
user |
api_key (env: LINKEDIN_LI_AT session cookie) |
verify.py |
|
playwright |
Playwright | mcp_local |
global |
none | verify.js (headless Chromium launch probe) |
A connector without verify is activated without any test — see § Without 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
connector.json;{folder}/{filename}in the index (e.g.gmail/icon_sm.svg).
File integrity (sha256)
- SHA-256 hashes are generated automatically by
scripts/compile.pyfrom the physical files present in each folder — never manual, never stale. compile.pyexcludes fromfiles[]:fragment.json,connectors.json,index.json,compile.sh,compile.py,update_hashes.py,.DS_Store, and thescripts/,__pycache__/,.git/directories. Everything else in the folder is hashed — includingconnector.json.fragment.jsonandindex.jsonhave no hash: they are compilation inputs only.- The only file signed (in the future) will be
connectors.json, the index itself.
Critical invariants
- Never edit
connectors.jsonby hand. It is generated. Editfragment.json/index.json/ the connector files, then runpython3 scripts/compile.py. - Bump
version(+1) andversion_stringon every file change to a connector, in bothfragment.jsonandconnector.json, and keepversion_release_datecurrent. - Keep
id,name,type,scope,tags,requires, andauthconsistent betweenfragment.jsonandconnector.json. - Adding a
verifyscript means shipping the file in the connector folder and recompiling — skald refuses to run a script whose SHA-256 is not pinned in the index. - Always run
python3 scripts/compile.pybefore deploying; a stale hash breaks integrity verification on the client. - The
secrets/directory is deprecated in the model (credentials flow through{ENV:}/{SECRET:}). Gmail still uses it locally pending the OAuth migration; that path is gitignored — never commit credentials or OAuth tokens.
Local workflow
-
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, env, verify, …) - Add the MCP script, icons,
verify.py,requirements.txt - Add the id to
connectors/index.json - Run
python3 scripts/compile.py
- Create the folder
-
Modifying an existing connector:
- Edit the files in the connector folder, bump
version/version_string/version_release_date - Do not touch
connectors.json— it gets regenerated - Run
python3 scripts/compile.py
- Edit the files in the connector folder, bump
-
Before deploying:
python3 scripts/compile.py # regenerates connectors.json with fresh SHA-256s python3 scripts/compile.py --verify # (optional) checks the index is up to date
Commands
Preview the catalog locally (must serve from the connectors/ root so /connectors.json resolves):
cd connectors && python3 -m http.server 8000 # then open http://localhost:8000/
Recompute a single file hash by hand (normally unnecessary — compile.py does it):
python3 -c "import hashlib; print(hashlib.sha256(open('connectors/gmail/gmail_mcp_server.py','rb').read()).hexdigest())"
Set up and run the Gmail connector standalone:
pip install -r connectors/gmail/requirements.txt
python3 connectors/gmail/gmail_mcp_server.py # speaks JSON-RPC on stdin/stdout
Deploy
The remote server:
- Host: skald-home-server (192.168.1.100 / 145.40.169.107)
- User: dguiducci
- Path:
/var/www/connectors.skaldagent.net/(served by Caddy) - Owner:
caddy:caddy— sudo required to write in/var/www/
Deploy runs deploy.sh on the server (git pull in
/home/dguiducci/repos/skald-connectors/, then sudo cp -r connectors/* /var/www/connectors.skaldagent.net/):
ssh dguiducci@skald-home-server /home/dguiducci/marketplace_deploy.sh
or via MCP SSH:
mcp__ssh__exec alias=skald-home-server command="/home/dguiducci/marketplace_deploy.sh"
Then verify on https://connectors.skaldagent.net/.
Remember to run python3 scripts/compile.py and commit before deploying — the server pulls from git.
Changelog
Every user-facing change to the marketplace (new connector, connector fix, version bump, icon update,
deploy notes, etc.) must be recorded in CHANGELOG.md at the repo root. Rules:
- Follow the classic Keep a Changelog format (version headings,
Added/Changed/Fixed/Removedcategories,[Unreleased]section at the top). Use date headings (## 2026-08-10) instead of semver, since each connector has its own version. - Write in English.
- Add the entry in the same commit/change that modifies the connector files — do not defer it.