Five example Model Context Protocol (MCP) servers, each built as a WebAssembly component with mcp-server-template-rs and deployable to Cosmonic Desktop. They are worked examples of the MCP-server "factory": the same template, the same guardrails, five different shapes of server.
Every example:
- speaks MCP 2026-07-28 (stateless streamable HTTP, header routing, structured output),
- exports
wasi:http/handler@0.3.0(WASI p3), - ships a hermetic
scripts/e2e.shtest suite (run in CI), - deploys to Cosmonic Desktop with a
workload.yaml(local builds) and adeploy/workload.yaml(the published image).
| Example | What it shows | Tools |
|---|---|---|
| after-effects-mcp | Driving a live desktop app via a polling bridge + wasi:keyvalue state |
create/animate compositions, layers, keyframes, expressions, masks, cameras, effects; render frames; plus pure-compute timecode/easing/colour helpers |
| premiere-mcp | Pure-compute with tricky domain math | SMPTE drop-frame timecode, offset, sequence duration, frame-rate conform |
| sec-edgar-mcp | Outbound HTTP, no key, caching | SEC EDGAR company facts & filing history (ticker→CIK, SEC_USER_AGENT) |
| cfpb-complaints-mcp | Outbound HTTP against a large, quirky dataset — faceted search, aggregations, and correcting misleading upstream semantics | CFPB consumer complaints: search, single record, facet ranking, time-series trends, company autocomplete |
| fred-mcp | Outbound HTTP with an API key | FRED economic-data search, series metadata & observations (FRED_API_KEY) |
Together they cover the whole framework surface: pure compute, domain-math correctness, outbound APIs, caching, secrets, host state, controlling a local application a component cannot call directly, faceted search over millions of records, and the concurrency/robustness patterns the template enforces (bounded buffers, deadlines, panic-free numeric code, DNS-rebinding and SSRF guards).
The reusable test harness is scripts/mcp_e2e_lib.sh:
each example's e2e.sh sources it for the framework-level checks (protocol,
spec enforcement, robustness, Host guard) and adds its own tool cases.
Prerequisites: Rust 1.90+ with the wasip2 target (rustup target add wasm32-wasip2); the e2e suite additionally needs wasm-tools,
wasmtime ≥ 46 (test harness only), python3, and curl.
$ cd after-effects-mcp # or any example
$ cargo build --release # -> target/wasm32-wasip2/release/<name>.wasm
$ ./scripts/e2e.sh # runs the hermetic test suitecargo test is not used — the build target is wasm, so scripts/e2e.sh
is the test entry point.
Deployment is via Cosmonic Desktop,
which runs WASI p3 components natively and routes ingress by HTTP Host
header. Each example ships two manifests: workload.yaml for a locally
built/promoted image, and deploy/workload.yaml for the published image.
The daemon's control socket is
~/Library/Application Support/Cosmonic/cosmonicd.sock.
If you just want to run one, skip to step 3 and apply deploy/workload.yaml:
it references a public image on GHCR
(ghcr.io/cosmonic-labs/mcp-examples/<name>:<version>), so nothing needs building. Steps 1
and 2 are for iterating on your own copy.
-
Register the project directory (it has a
.wash/config.yaml):$ SOCK="$HOME/Library/Application Support/Cosmonic/cosmonicd.sock" $ curl --unix-socket "$SOCK" -X POST http://localhost/v1/projects \ -H 'Content-Type: application/json' \ -d '{"path":"'"$PWD"'"}'
-
Promote — build and push to the daemon's built-in registry. This returns a digest-pinned image reference; use it in the workload.
$ curl --unix-socket "$SOCK" -X POST \ http://localhost/v1/projects/<name>/promote \ -H 'Content-Type: application/json' \ -d '{"ref":"oci-registry.localhost:8200/<name>:0.1.0","insecure":true}'
Note: at the time of writing the
cosmonic_promoteMCP tool sends the wrong field name (referenceinstead ofref); the socket call above is the reliable path. -
Apply the workload —
workload.yamlwith the digest-pinned image from promote, ordeploy/workload.yamlfor the published image — via the Cosmonic Desktop UI, thecosmonic_apply_workloadMCP tool, orPOST /v1/workloads. Key fields:spec: hostInterfaces: - namespace: wasi package: http interfaces: ["handler"] # p3 handler — NOT incoming-handler config: { host: <name>.localhost } components: - name: mcp image: oci-registry.localhost:8200/<name>:0.1.0@sha256:… localResources: environment: config: MCP_ALLOWED_HOSTS: "<name>.localhost" # matches the ingress host allowedHosts: [ … ] # outbound allow-list (deny-all if empty)
-
Call it through the ingress (routes by
Host):$ curl -X POST http://<name>.localhost:8200/ \ -H 'Content-Type: application/json' \ -H 'Accept: application/json, text/event-stream' \ -H 'MCP-Protocol-Version: 2026-07-28' \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2026-07-28","capabilities":{},"clientInfo":{"name":"you","version":"0"}}}'
A successful response is SSE-framed (
data: {…"protocolVersion":"2026-07-28"…}). Tool calls addMcp-Method: tools/call,Mcp-Name: <tool>, and a per-request_meta(see each example's README for a full call).
- Host header guard: the template accepts only localhost by default. On
Desktop, set
MCP_ALLOWED_HOSTSto the workload's ingress host, or requests are rejected withForbidden. - Outbound allow-list:
allowedHostsis deny-all when empty. List every upstream host a tool dials (e.g.www.sec.gov,data.sec.gov,api.stlouisfed.org). - Secrets: for API keys, register a Cosmonic secret and reference it with
secretFromrather than inlining the value — seefred-mcp. - Don't overwrite existing workloads: apply is idempotent by
namespace/name, and the ingresshostis global. Pick a fresh namespace/name/host if one is taken.
Each example is released independently, from a tag named
<example>/v<version> whose version matches that example's Cargo.toml:
$ git tag fred-mcp/v0.1.0
$ git push origin fred-mcp/v0.1.0.github/workflows/release.yml then checks
the manifests, builds the component, runs its e2e suite, and only on success
pushes it to ghcr.io/cosmonic-labs/mcp-examples/<example> — as an OCI wasm artifact
(application/wasm layer, pushed with wkg), not a container image — under
both <version> and latest. It finishes by cutting a GitHub Release whose
notes carry the digest-pinned reference. Use workflow_dispatch (with
dry_run) to rehearse a release without publishing.
The version in the tag, in Cargo.toml, and in the image reference in
deploy/workload.yaml must agree, or the release fails before pushing
anything — this is what stops a manifest from pointing at an image that was
never published. scripts/check-manifests.sh
enforces that on every PR too, along with the rule that every ingress host
appears in MCP_ALLOWED_HOSTS (miss that and the deployment answers nothing
but Forbidden).
To bump an example: edit its Cargo.toml version, update app.kubernetes.io/version
and the image tag in both manifests, run ./scripts/check-manifests.sh, merge,
then tag.
.github/workflows/ci.yml runs every example's
e2e suite, cargo fmt --check, and the manifest check on each push and PR.
These examples are the reference implementations for the
building-mcp-servers skill that ships in the
template repo.
Start from the template, follow the skill's phases (scaffold → tools →
gates → e2e → deploy), and consult its living "Pitfalls" list — most of which
was learned building exactly these four servers.
Apache-2.0