# Changelog All notable changes to the Skald Connectors Marketplace are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0/). ## [Unreleased] ## 2026-08-19 ### Added - New connector: **Playwright** (mcp_local, scope global, v1 / 1.0.0) — full browser automation via the official `@playwright/mcp@0.0.79` (Microsoft), wrapped like http-fetch/firecrawl: `package.json` pins the package and `index.js` rewrites argv (`--headless --isolated --no-sandbox`, plus a `process.argv.slice(2)` passthrough) and imports the package's `cli.js`, which self-executes at import time. `cli.js` is not in the package `exports` map, so the wrapper resolves it from the exported `package.json` path. - `auth: none`, `requires: ["NODE"]`. Runs headless with an **isolated in-memory profile**: no cookies/login state persisted between sessions (chosen over the default persistent profile — a shared household browser must not accumulate per-user sessions, and a persistent profile also allows only one browser instance at a time). - **Browser download**: the upstream `playwright` package does NOT download browsers at `npm install` (verified on 1.63.0-alpha-2026-08-05), and `@playwright/mcp` 0.0.79 no longer ships the old `browser_install` tool (the README section is now empty). So the connector's `package.json` carries a `postinstall` hook — `node node_modules/@playwright/mcp/cli.js install-browser chromium` — which downloads Chromium + headless shell + ffmpeg only (~350 MB; a bare `install-browser` would also fetch Firefox and WebKit). Verified on a cold directory: `npm install --omit=dev` runs the hook and the browser launches. - `verify.js` (30s) resolves `playwright-core` relative to `@playwright/mcp` (it is a transitive dep, not declared by the connector's own package.json) and launches a real headless Chromium on about:blank — catches the two real failure modes (binary missing, system libraries missing on slim hosts) before the activation is saved. - Default tool set = 24 tools (no `--caps` extras); all get `display_name` in `tools[]` because `@playwright/mcp` does not emit MCP `title` fields in `tools/list` (the "Title:" lines in its README are not on the wire). - ⚠️ Security: the default set includes `browser_run_code_unsafe` (RCE-equivalent, arbitrary JS in the server process) and `browser_evaluate` (arbitrary JS in the page); there is no CLI flag to disable individual core tools. Consistent with the `mcp_local` trust model (guide §3) and declared in the manifest descriptions. - Icons: official Playwright SVG (Microsoft catalog), sized 48/96. - Tested E2E reproducing skald's path (`npm install --omit=dev` + `node index.js`): `initialize`, `tools/list` (24 tools), real `browser_navigate` + `browser_snapshot` on https://example.com ✅, verify probe ok ✅ ## 2026-08-10 ### Fixed - **http-fetch + firecrawl: `npx` launch was broken (v5 / 1.1.0)** — both connectors declared `mcp_config: {command: "npx", args: ["-y", ""]}` and shipped no code files (only `connector.json` + icons). - `npx -y ` is not expressible in skald. For a `type: mcp_local`, skald treats `args[0]` as the **name of the file to run**, not as an argument: at install it computes `script_path = "/" + args[0]` and clears `args_json` (`marketplace.rs::install`), then `global_enable` (`api/mcp.rs`) resolves it to an absolute path and launches ` `. The real command became `npx /…/connectors/http-fetch/-y` — a nonexistent path, with `-y` and the package name lost. The process never answered `initialize`, so `start_server` failed. - The failure was silent: `global_enable` still returns HTTP 200 with an `error` field in the body, so the UI showed the connector as enabled while the runtime had no server. And `render_mcp_list` (`loop_adapters/system.rs`) builds the `## MCP servers` table from `mcp.tools()`, i.e. the **live runtime state**, not the DB → the connector appeared activated and granted to the user but **absent from the system context**. ⚠️ This combination (200 + `error` in the body) makes any connector that fails to start invisible in the UI: worth surfacing on the skald side. - Fix — two-file wrapper for both: a `package.json` pinning the upstream package (`mcp-fetch-server@1.1.2`, `firecrawl-mcp@3.23.7`) and an `index.js` that imports it for side effects (the module starts the JSON-RPC loop on stdio at import). `mcp_config` becomes `{command: "node", args: ["index.js"], transport: "stdio"}`, i.e. a real `local_script`: `ensure_installed_host` runs `npm ci --omit=dev || npm install --omit=dev` in the connector folder before launch, exactly like whatsapp. No `node_modules` shipped, no lockfile (like whatsapp). - Removed legacy fields `launch_command`, top-level `transport`, and `dependencies` (`dependencies` is only for the card, as already seen on gmaps; `transport` belongs inside `mcp_config`). - firecrawl: removed `mcp_config.env: {"FIRECRAWL_API_KEY": "{SECRET:FIRECRAWL_API_KEY}"}` — inert, same case as gmaps on 2026-08-10: `apply_key_placeholder` substitutes tokens only in the URL, never in `env` values. It worked because the admin form sends `env` and that payload overwrites `entry.env_json`. - firecrawl: added `firecrawl_developer_search` to `tools[]` (27 live tools vs 26 declared, verified on 3.23.7); `requires` `["NODE"]` → `["NODE", "API_KEY"]`. - Re-aligned manifest↔fragment versions to `5` / `1.1.0` / `2026-08-10` for both: they were 2/1.0.1 (manifest) vs 4/1.0.3 (fragment), and skald prefers the manifest — so `installed_version` stayed at 2 and the "Update available" badge would never have appeared. - Host requirement: these are `scope: global` connectors, they run on the **host**, not in the container. `mcp-fetch-server` wants Node ≥18, `firecrawl-mcp` wants Node ≥**22**. - Tested end-to-end reproducing skald's path (`npm ci || npm install` + `node /index.js`): `initialize`, `tools/list`, and a real `tools/call`, stdout only JSON-RPC, clean stderr ✅ - Index regenerated with compile.py ✅ - ⚠️ **Applying this to an instance that already has the connector installed is not just an Update.** `refresh_connector_after_reinstall` (`skald/accessors.rs`) updates only the `description` of the `mcp_global_servers` row, then restarts from that row: `command` and `args_json` stay the ones snapshotted at the first `global_enable`, i.e. still `npx` + `/…/connectors//-y`. Procedure: deploy → **Update** from the marketplace (rewrites `script_path` in the catalog) → open the connector page and **re-save the config**, the only call that recomputes `command`/`args` and runs `ensure_installed_host`. Same care already noted for gmaps. Whether the refresh should also re-derive command/args is open for evaluation on the skald side. - **gmaps: missing dependency install + verify wired (v6 / 1.1.0)** - Added `requirements.txt` (`googlemaps>=4.10.0`) — it was the only python connector without one. Dependencies were declared in the manifest `dependencies` field, which skald uses **only for the card**: `ensure_installed_host` looks exclusively at `requirements.txt` / `package.json`. Result: empty `.pydeps` and logs full of `No module named 'googlemaps'`, with the server still answering `tools/list` (→ `connected — 6 tool(s)` on a broken connector). - Wired the `verify` (`python3 verify.py`, 20s): `verify.py` was shipped but the manifest had no `verify` block, and skald reads `verify_command` only from there. Now an activation with broken dependencies fails visibly instead of starting silently. - `verify.py` puts `.pydeps` on `sys.path`: skald sets `PYTHONPATH` only for the *server* process (`global_row_spec`), while verify runs as `sh -c "python3 verify.py"` without it. Without this line, verify would fail with "Missing dependency" even on a correctly installed connector, disabling the row. ⚠️ **Same latent risk for every connector with a `verify` that imports non-stdlib dependencies** (gcal in container): to be checked. - Removed `mcp_config.env: {"GOOGLE_MAPS_API_KEY": "{SECRET:…}"}` (introduced 2026-07-23): inert. `apply_key_placeholder` substitutes `{SECRET:}`/`{ENV:}` tokens **only in the URL**, never in `env` values. It worked because the admin form sends `env` and that payload overwrites `entry.env_json`; with an empty form the process would have received the literal string. ⚠️ **The spec in this file and in CLAUDE.md says the opposite** — either fix the spec, or extend the substitution to `env` values on the skald side. - Re-aligned manifest↔fragment versions to `6` / `1.1.0` / `2026-08-10`: they were 2/1.0.1 vs 5/1.0.4, and skald prefers the manifest (`manifest.version.or(entry.version)`) — so `installed_version` stayed at 2 and the "Update available" badge would never have appeared. - `requires`: `ENV` → `API_KEY`; server error messages cleaned of references to `secrets/gmaps_api_key.txt` (deprecated path). - Index regenerated with compile.py ✅ ## 2026-08-07 ### Added - New connector: **LinkedIn** (mcp_local, scope user): server.py + session.py + verify.py + PNG icons - Added to `connectors/index.json`, index regenerated with compile.py (17 connectors total) - `.gitignore` updated to ignore `.claude/` - Deploy to connectors.skaldagent.net via `skaldserver` alias (192.168.1.100, LAN — no Tailscale) ## 2026-07-23 ### Changed - **gmaps: env var injection fix** — added `mcp_config.env` in `connector.json` to inject `GOOGLE_MAPS_API_KEY` into the MCP process. The connector was declared as `delivery: env` but without `mcp_config.env` Skald could not pass the variable to the Python process. Version bump: fragment 4→5, connector 1→2. Index regenerated with compile.py ✅ - **Context7 icon update (PNG)** — replaced Context7 icons from SVG to PNG (new icon provided by the user): `icon_sm.png` — 48×48 (2.7 KB), `icon_lg.png` — 96×96 (4.5 KB). Old SVGs removed, references updated in fragment.json and connector.json. Version bump: fragment 3→4, connector 1→2. Index regenerated with compile.py ✅ - **SerpAPI Flights icon update (PNG)** — replaced SerpAPI Flights icons from SVG to PNG: `icon_sm.png` — 48×48 (2.9 KB), `icon_lg.png` — 96×96 (6.8 KB). Old SVGs removed, references updated in fragment.json and connector.json. Version bump: fragment 4→5, connector 2→3. Index regenerated with compile.py ✅