docs: replace connector manifest guide with coding agent version, adapted for compile.py workflow

This commit is contained in:
2026-07-22 23:26:52 +01:00
parent b97de67176
commit c151739021
2 changed files with 72 additions and 29 deletions
+70 -28
View File
@@ -11,13 +11,16 @@ every file against a SHA-256 pinned in the index, then either runs it on the hos
---
## 1. The two documents
## 1. The two documents (+ one build script)
### 1a. The root index — `connectors.json`
### 1a. The compiled root index — `connectors.json`
One array of entries, each pointing at a connector folder. **The index is the
signable root: it is the only place that lists a connector's files and their
SHA-256 digests.** Skald refuses any file whose bytes do not match.
**This file is auto-generated.** Do not edit it by hand. It is produced by
[`scripts/compile.py`](../scripts/compile.py), which reads `index.json` + the
`fragment.json` in each connector folder, scans the physical files for SHA-256
digests, and writes the final index.
The output schema looks like this:
```jsonc
{
@@ -37,9 +40,6 @@ SHA-256 digests.** Skald refuses any file whose bytes do not match.
"requires": ["NODE"], // human hint: NODE | PYTHON | OAUTH | API_KEY
"tags": ["messaging", "mcp", "local", "whatsapp", "qr"],
"auth": { "type": "qr" }, // may be repeated here and in the manifest
"tools": [ // OPTIONAL — friendly UI names per tool (§2a)
{ "name": "send_message", "display_name": "Send Message" }
],
"folder": "whatsapp", // defaults to id
"files": [
{ "path": "index.js", "sha256": "…", "size": 21258 },
@@ -58,14 +58,28 @@ SHA-256 digests.** Skald refuses any file whose bytes do not match.
- `files[].path` is relative to the connector folder. List **every** file the
connector ships (server code, `package.json`/`requirements.txt`, icons, and the
`connector.json` itself). A missing or mismatched digest fails the install.
- Compute `sha256` over the exact bytes served: `sha256sum <file>`.
- SHA-256 digests and file sizes are **computed automatically** by `compile.py`.
Never write them by hand.
- The excluded files (`fragment.json`, `connectors.json`, `index.json`,
`.DS_Store`) are handled by `compile.py` — you don't need to think about them.
- Do **not** list `node_modules/` or any generated deps — those are installed on
the box, not shipped (see §5).
- `size` is optional but recommended.
- `tools[]` is optional but, when present in the manifest, should also be listed here
so that the index is self-contained (the index is the only document Skald downloads
eagerly — metadata UIs that need friendly tool names look here first, §2a).
Structure: `{ "name": "<raw>", "display_name": "<friendly>" }`.
- `size` is optional but the compiler includes it.
#### How the index is built
Instead of editing `connectors.json` directly, you maintain **two lightweight
source files** and run one command:
1. **`connectors/index.json`** — a flat JSON array of folder ids in display
order: `["gmail", "gcal", "myconn", …]`
2. **`connectors/<id>/fragment.json`** — the connector's index entry with
**every field except `files[]`** (same schema as above minus that array).
3. **`python3 scripts/compile.py`** — reads `index.json`, loads each
`fragment.json`, scans the folder for real files, computes SHA-256, and
writes `connectors.json`.
The `tools[]` block (friendly UI names, §2a) goes into `fragment.json`.
### 1b. The per-connector manifest — `<folder>/connector.json`
@@ -107,6 +121,12 @@ learns which file to run. At activation Skald rewrites it to the file's path
inside the user's container (`/root/.skald/mcp/<name>/<entry>`), so keep it a
plain relative filename (`index.js`, `server.py`, `pkg/server.py`).
**`llm_short_description` is model-facing:** it's the one-liner injected into
the LLM's system prompt so the model knows what this connector does. Keep it
short and functional ("Weather — current conditions, 16-day forecast, and AQI
data for any location"), **not** a list of tools (the model discovers tools
after `activate_tools`).
---
## 2. Server contract (MCP over stdio)
@@ -308,14 +328,31 @@ Three fields, in **both** the index entry and the `connector.json`, kept identic
- `version` is a **number, not a string** (`1`, not `"1"` or `"2.0.1"`). Start at
`1` for the first release under this scheme; **`+1` on every change** to any
shipped file. Never reuse or decrement.
shipped file **or to any manifest metadata** (description, icons, `version_string`).
Never reuse or decrement.
- Skald stores the installed `version` and compares it to the feed's: a strictly
greater feed `version` shows **"update available"** in the marketplace, and the
Install button becomes **Update**. Clicking it re-downloads the files and users
pick up the new code + deps on their next login.
- **The integer is the UI "is there an update?" signal — the actual re-install
trigger is the content-hash (§5).** So an update still propagates even if the
number is not bumped; but always bump it, or the admin never sees the update.
Install button becomes **Update**. Clicking it re-downloads the files and rewrites
the catalog row.
- **The integer is the *only* "is there an update?" signal**it is compared
strictly (`feed > installed`). `version_string` (semver), icons and
`llm_short_description` are **never** compared, so a change to any of them that
does not also bump the integer is **invisible**: no "update available" badge
appears. This is the common trap — a "content-only" edit (e.g. a better
`llm_short_description`) that forgets the integer.
- **Two propagation paths, do not conflate them:**
- *Per-user code + deps* (the scripts, `package.json`/`requirements.txt`) reconcile
on a **content-hash** of the source files (§5), so new code lands at each user's
next login even without a reinstall.
- *Catalog metadata* (`llm_short_description` → the model's prompt, icons, friendly
name) is **not** in that hash — it lives in the catalog row and is rewritten only
by an explicit **reinstall/Update**. On reinstall Skald re-pulls the current feed
(never the browse cache) and pushes the new description live: enabled global
servers restart with it, and every logged-in user who activated the connector has
it restarted with the fresh `llm_short_description` — no re-login needed.
- So: to ship a new `llm_short_description`, **bump the integer** (so the admin sees
"update available") and the admin clicks **Update**. Nothing auto-propagates a
description change.
- `version_string` and `version_release_date` are display metadata only — never
compared. (Migration note: replace any legacy string `"version": "2.0.1"` with
the integer `version` + `version_string`.)
@@ -327,12 +364,17 @@ Three fields, in **both** the index entry and the `connector.json`, kept identic
1. Folder `myconn/` with: entry file, `connector.json`, deps file
(`package.json`/`requirements.txt`), `icon_sm.svg`, `icon_lg.svg`,
optional `verify.*`.
2. Server speaks MCP over stdio (§2); **stdout = JSON-RPC only**.
3. `mcp_config.args[0]` names the entry file.
4. Correct `type` + `scope` (§3) and `auth.type` (§4).
5. For `qr`: implement `login_status` (+ `logout`), persist the session under the
2. Create `fragment.json` inside the folder (same schema as the index entry
without `files[]` — include `tools[]` if needed).
3. Add `"myconn"` to `connectors/index.json`.
4. Run `python3 scripts/compile.py` (auto-generates `connectors.json` with
fresh SHA-256 digests).
5. Server speaks MCP over stdio (§2); **stdout = JSON-RPC only**.
6. `mcp_config.args[0]` names the entry file.
7. Correct `type` + `scope` (§3) and `auth.type` (§4).
8. For `qr`: implement `login_status` (+ `logout`), persist the session under the
connector dir (§4d).
6. Deps declared as a file, **not** vendored (§5).
7. Add the entry to `connectors.json` with a correct `sha256` for **every** file.
8. Bump `version`.
```
9. Deps declared as a file, **not** vendored (§5).
10. Bump `version`.
11. Run `python3 scripts/compile.py --verify` to confirm the index is fresh,
then commit.