Skip to content

Write a plugin

A plugin extends Speedwave with a containerized MCP worker Speedwave can call, or with a bundle of skills, commands, agents, and hooks. This page covers what a plugin is made of and how to get from source to a package Speedwave loads. Plugins are written, developed, and shipped exclusively by Speednet: a plugin built from this guide reaches users only after Speednet signs and releases it. If your plugin ships a worker, the Write an MCP server guide covers its boot contract and tool conventions.

Writing a plugin means producing source, not a finished package. You write a manifest and, for an MCP plugin, worker code and a container definition. Speednet turns that source into a signed ZIP, and Speedwave builds the image locally from verified contents.

A plugin.json manifest with a service_id, a Containerfile, and the worker source. It may also ship a claude-resources directory.

The manifest needs name, slug, version, and description. The slug is the identifier everything else derives from: install directory, config key, compose service name, worker environment variable, and token directory. It must match ^[a-z][a-z0-9-]{0,63}$ and cannot collide with a built-in service, whether by ID (slack, github, office), by derived compose name (mcp-<slug>), or bare (a slug of claude is rejected). For an MCP service plugin, service_id must equal slug.

Optional fields cover credentials (auth_fields), a settings form (settings_schema), setup notes (instructions), an OAuth flow (oauth), a desktop companion link (host_bridge), and resource caps (mem_limit, cpu_limit). See Plugin mechanism for size limits and validation rules behind each field.

For an MCP plugin, your Containerfile builds a worker that inherits Speedwave’s container hardening: dropped capabilities, no new privileges, a read-only root filesystem, and a noexec tmpfs at /tmp. None of this is configurable, so design the worker to run unprivileged with nothing written to the root filesystem, within a cap of 16 GiB memory and 4 CPUs.

Two mounts cross the container boundary: /tokens, always read-only, where the worker reads credentials from /tokens/<key> and can never write; and /workspace, read-write, the only surface for state the worker needs to persist.

If the plugin authenticates through OAuth, declare an oauth block: Speedwave’s own oauth worker drives authorization and refresh, so long-lived secrets stay host-side and only a short-lived bearer token reaches the container. Speedwave supports three grant types, each tied to a required manifest field: authorization_code needs oauth.authorize_url, device_code needs oauth.device_authorization_url, and client_credentials needs oauth.client_secret_field. Any endpoint URL you provide is validated before Speedwave accepts the manifest.

  1. Write plugin.json, the Containerfile (for an MCP plugin), the worker source, and any claude-resources.

  2. Send the source to Speednet for review and signing. Speednet builds the ZIP, signs it with the Ed25519 private key, and adds a SIGNATURE file.

  3. Install the signed package in Speedwave, which verifies the signature against its embedded Speednet public key.

  4. Speedwave runs install-time validation, then builds a local image from the verified source.

  5. Enable the plugin for a project and fill in any credentials the manifest asks for.

Before you send the source off, test it locally: build the Containerfile, mount /tokens read-only and /workspace read-write, and confirm the worker starts unprivileged against a read-only root. That is the shape Speedwave enforces after install, so a mismatch caught now saves a signing round trip.

A plugin can reach its own /workspace and read its own /tokens. It cannot write credentials, escalate privileges, or reach another plugin’s token directory. Speedwave re-verifies the signature every time it renders compose, builds the image, or lists the plugin in Desktop, so an altered package stops loading and nothing in plugin.json lets you self-sign or relax the hardening.

For how signature verification, the manifest schema, and storage work underneath, see Plugin mechanism.