Skip to content
Scott Shwarts edited this page Jul 18, 2026 · 1 revision

0.34.0 — REST-backed connectors + secret hardening

Two things landed together: making it obvious that a plain REST/HTTP API is a first-class connector (no MCP server required), and closing a spot where secrets could leak into shell child processes.

Upgrade impact: none (additive), with one security tightening — see "Do I need to change anything?" below.

Your REST API is already a connector

If you've been assuming you need to stand up an MCP server just to let a skill call your HTTP API — you don't. McpConnector is the dispatch contract for $ connector.tool ops, and "MCP" names the verb, not the wire. A connector fronting a plain REST endpoint is a first-class citizen; MCP JSON-RPC framing is just one implementation (HttpMcpConnector).

There's now a complete, runnable worked example — examples/connectors/RestConnector/ — that fronts a REST API: an ENDPOINTS table maps each tool to method + path, call() does path-templating, query-vs-body routing, auth-header injection, and timeout; staticTools() gives lint a closed tool set; describeTools() surfaces the endpoints. Not a throws TODO skeleton — copy it and adapt.

MCP and REST connectors coexist in one registry. A single skill body can mix them freely:

run:
    $ gmail.send_message to="${WHO}" subject="Done" -> SENT     # an MCP connector
    $ tickets.create title="${SUMMARY}" priority="high" -> TICKET  # a REST connector

New in this release for connector authors: McpToolDescriptor is re-exported from skillscript-runtime/connectors (you need the named type to write describeTools()), and docs/connector-contract-reference.md gained a full McpConnector section.

Secrets no longer leak into shell children

Before: a shell(...) op's child process inherited the runtime's whole environment — including your SKILLSCRIPT_SECRET_* values. Now those are scrubbed from shell children, on both the structured (command=/argv=) and the unsafe=true bash path.

Why it's safe to scrub: the runtime resolves a {{secret.NAME}} marker and splices the value straight into the spawn argv at the sink, so a shell child never needed the raw secret in its ambient environment in the first place. Scrubbing makes your # Requires: declaration authoritative (least-privilege enforced) rather than advisory.

Egress vars are preserved — HTTPS_PROXY, NODE_EXTRA_CA_CERTS, CURL_CA_BUNDLE, SSL_CERT_FILE still pass through, so the outbound-proxy / credential-free-egress pattern is unaffected.

Do I need to change anything?

Almost certainly no. The one case affected: a skill that read an undeclared secret straight out of a shell child's environment (e.g. curl picking up $SOME_SECRET implicitly). That never was a supported pattern — the supported way a secret reaches a command is a {{secret.NAME}} marker with a matching # Requires: secret.NAME. If you had a skill relying on the implicit-env behavior, declare the secret and place the marker:

# Requires: secret.API_KEY
run:
    shell(command="curl -H 'Authorization: Bearer {{secret.API_KEY}}' https://api...") -> R

Everything else is backward-compatible — no config edits, no re-approval.

Clone this wiki locally