{"id":"a332ee00-b63b-4444-b238-88f82a0b9468","slug":"soniccraft","kind":"mcp","name":"soniccraft","summary":"Declarative multi-track audio engine for AI agents: YAML timelines to TTS speech, music beds and SFX with sidechain ducking, EBU R128 mastering and subtitles. CLI, MCP server, Agent Skill and Claude C","owner":"user-github-me","repoUrl":"https://github.com/user-github-me/soniccraft","license":"Apache-2.0","licenseTier":"permissive","trust":"community","stars":1,"match":null,"fewStars":true,"format":"mcp","createdAt":"2026-10-19T21:58:12.000Z","addedAt":"2026-10-20T14:59:19.370Z","logoUrl":"https://avatars.githubusercontent.com/u/75520535?u=439377550cc5d32ce432109ad8a6134d9dd9c200&v=4","linkOnly":false,"collection":null,"skillPath":null,"categories":["design-media","developer-tools","cloud-infra"],"tags":["ai-agents","audio","claude-code","loudness","podcast","rust","subtitles","text-to-speech"],"runtime":"docker","local":true,"cloudCapable":false,"cloudCapability":"unknown","cloudReadyOnPlatform":false,"needsApiKey":false,"reviewed":false,"syncedAt":"2026-10-20T15:03:29.487Z","sample":false,"url":"https://conduits.dev/tools/soniccraft","language":"Rust","transports":["stdio"],"install":{"docker":"ghcr.io/user-github-me/soniccraft:0.1.0"},"requirements":{"env":[{"name":"ELEVENLABS_API_KEY","secret":true,"required":false,"description":"ElevenLabs API key for the elevenlabs TTS provider (optional)"},{"name":"OPENAI_API_KEY","secret":true,"required":false,"description":"OpenAI API key for the openai TTS provider (optional)"},{"name":"CARTESIA_API_KEY","secret":true,"required":false,"description":"Cartesia API key for the cartesia TTS provider (optional)"}],"upstreamMayCharge":false},"tools":null,"readme":"<p align=\"center\">\n  <picture>\n    <source media=\"(prefers-color-scheme: dark)\" srcset=\"assets/brand/wordmark-dark.png\">\n    <img src=\"assets/brand/wordmark-light.png\" alt=\"SonicCraft\" width=\"420\">\n  </picture>\n</p>\n\n# SonicCraft\n\n**A declarative, deterministic audio engine for AI agents.** Describe a production as a YAML (or JSON)\ntimeline: who says what, in which order, over which music, at what loudness. SonicCraft compiles it into\nTTS speech, music beds and sound effects, mixed with sidechain ducking, mastered to an EBU R128 loudness\ntarget, plus WebVTT/SRT subtitles that match the audio it actually rendered. It ships as a CLI, an MCP\nserver and an Agent Skill.\n\n[![CI](https://github.com/user-github-me/soniccraft/actions/workflows/ci.yml/badge.svg)](https://github.com/user-github-me/soniccraft/actions/workflows/ci.yml)\n[![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](#license)\n[![crates.io](https://img.shields.io/crates/v/soniccraft.svg)](https://crates.io/crates/soniccraft)\n[![SonicCraft MCP on mcprush](https://mcprush.com/badge/soniccraft-mcp/score.svg)](https://mcprush.com/to/soniccraft-mcp?ref=badge)\n\n**[Website](https://user-github-me.github.io/soniccraft/)** · [Quickstart](#quickstart) ·\n[Use it from an AI agent](#use-it-from-an-ai-agent) · [MCP server](#mcp-server) · [Agent Skill](#agent-skill)\n\n## Use it from an AI agent\n\nGive an agent (Claude Code, Codex, Cursor, Gemini CLI, ...) the link to this repository and ask for audio, for\nexample *\"Use https://github.com/user-github-me/soniccraft to make a 30-second voiceover with background music\nfor my product video.\"* Everything it needs is below and in [AGENTS.md](AGENTS.md).\n\n1. **Install the CLI** (prebuilt and checksum-verified, no Rust needed). Homebrew, Scoop, Docker and Cargo work\n   too: see [Quickstart](#quickstart).\n\n   ```bash\n   curl -fsSL https://raw.githubusercontent.com/user-github-me/soniccraft/main/install.sh | sh   # macOS, Linux\n   ```\n\n   ```powershell\n   irm https://raw.githubusercontent.com/user-github-me/soniccraft/main/install.ps1 | iex        # Windows\n   ```\n\n2. **Write a timeline.** `soniccraft init my-audio` starts one that renders offline: macOS voices on a Mac,\n   elsewhere a mock test voice (not real speech). Real voices anywhere: Kokoro or Piper (free, local) or an\n   ElevenLabs, OpenAI or Cartesia key, see [Providers](#providers). `soniccraft init --list` shows the\n   templates, `soniccraft providers` the voices usable here, and `soniccraft schema` the JSON Schema; the\n   [Agent Skill](skills/soniccraft/SKILL.md) explains the format.\n3. **Check it, see the cost, render it:**\n\n   ```bash\n   soniccraft validate my-audio/timeline.yaml           # errors with a code, line, column and fix hint\n   soniccraft plan my-audio/timeline.yaml               # free dry run: timing, ducking, characters to synthesize\n   soniccraft render my-audio/timeline.yaml --preview   # mastered audio, subtitles, manifest, preview page\n   ```\n\nTo keep SonicCraft in an agent for good, add the skill and the MCP server. Both use the CLI from step 1; with\nNode.js 20 or newer the MCP server also runs with nothing installed: use `npx -y soniccraft-mcp` in place of\n`soniccraft mcp` below.\n\n| Agent | Skill | MCP server |\n|---|---|---|\n| Claude Code | `claude plugin marketplace add user-github-me/soniccraft`, then `claude plugin install soniccraft@soniccraft` | included in the plugin, or `claude mcp add soniccraft -- soniccraft mcp` |\n| Codex | `npx skills add user-github-me/soniccraft -g -a codex` | `codex mcp add soniccraft -- soniccraft mcp` |\n| Cursor | `npx skills add user-github-me/soniccraft -g -a cursor` | `{\"mcpServers\": {\"soniccraft\": {\"command\": \"soniccraft\", \"args\": [\"mcp\"]}}}` in `.cursor/mcp.json` |\n| Gemini CLI and others | `npx skills add user-github-me/soniccraft -g` (choose the agent) | the same `mcpServers` entry (Gemini CLI: `~/.gemini/settings.json`) |\n\nThe server is also in the [official MCP Registry](https://registry.modelcontextprotocol.io) as\n`io.github.user-github-me/soniccraft` (container image). Details: [MCP server](#mcp-server) and\n[Agent Skill](#agent-skill).\n\n## Why\n\nAsk an agent for a two-voice explainer with background music and it will usually hand-write an ffmpeg\nfilter graph: `adelay` offsets computed from guessed speech lengths, an `amix`, perhaps a half-remembered\n`sidechaincompress`. That fails in predictable ways:\n\n- **Timing drift and overlaps.** Synthesized speech is never exactly as long as the agent guessed, so lines\n  collide or leave dead air, and every script edit shifts every later offset.\n- **No ducking.** Sidechain compression is fiddly to get right in a filter graph, so the music either\n  drowns the voice or sits uniformly too low.\n- **Loudness disparity.** Different providers and voices come out at different levels. Nothing measures\n  integrated loudness or true peak, so the result is uneven and can clip.\n- **Drifting subtitles.** Captions are written from estimated times instead of from the audio that was\n  rendered.\n- **Opaque, expensive iteration.** A broken graph is a cryptic ffmpeg error, and every retry re-bills every\n  TTS call.\n\n## How SonicCraft works\n\n[HyperFrames](https://github.com/heygen-com/hyperframes) showed that agents make better video when they\nwrite declarative HTML and a deterministic renderer does the frame-accurate work. SonicCraft applies the\nsame idea to audio. The agent writes **intent as data**; a **compiler** validates it, measures the real\ndurations, resolves all timing in integer sample frames, and renders natively in Rust. The same timeline,\nTTS cache and binary always produce byte-identical audio, subtitles and manifests.\n\n```yaml\nversion: \"1.0\"\noutput:\n  format: \"mp3\"\n  sample_rate: 48000\n  bitrate: \"320k\"\n  target_lufs: -16.0 # integrated loudness target, measured per EBU R128 / ITU-R BS.1770\n  export_subtitles: true # Emits matching .vtt and .srt\n\ntracks:\n  - id: ambient_bed\n    type: \"music\"\n    source: \"assets/audio/synthwave_loop.wav\"\n    loop: true\n    volume: 0.65\n    fade_in_ms: 1200\n    fade_out_ms: 2500\n    ducking:\n      trigger_tracks: [\"voice_primary\", \"voice_secondary\"]\n      reduction_db: -18.0\n      attack_ms: 80\n      release_ms: 350\n\n  - id: voice_primary\n    type: \"speech\"\n    pan: -0.15 # Subtle left stereo placement\n    sequence:\n      - id: cue_01\n        speaker: \"narrator\"\n        provider: \"elevenlabs\"\n        voice_id: \"21m00Tcm4TlvDq8ikWAM\"\n        text: \"Autonomous software development requires deterministic compilation targets.\"\n      - pause_duration: 350ms\n      - id: cue_03\n        speaker: \"narrator\"\n        provider: \"elevenlabs\"\n        voice_id: \"21m00Tcm4TlvDq8ikWAM\"\n        text: \"By separating high-level planning from digital signal processing, systems eliminate runtime hallucinations.\"\n\n  - id: voice_secondary\n    type: \"speech\"\n    pan: 0.15 # Subtle right stereo placement\n    sequence:\n      - id: cue_02\n        start_relative:\n          after_cue: \"cue_01\"\n          offset_ms: 150\n        speaker: \"guest\"\n        provider: \"local_kokoro\"\n        voice_id: \"am_adam\"\n        text: \"Exactly. SonicCraft treats audio composition the same way Hyperframes treated web-based video.\"\n\n  - id: sfx_impact\n    type: \"sfx\"\n    source: \"assets/sfx/whoosh.wav\"\n    volume: 0.8\n    start_at_ms: 0\n```\n\nThis is [`examples/tech-briefing/timeline.yaml`](examples/tech-briefing/timeline.yaml). Here is what\n`soniccraft render` does with it:\n\n1. **Validates everything at once.** Unknown keys, misspelled enum values, dangling anchors, missing\n   voices, missing files and missing API keys are all reported together, each with a stable code, a path,\n   a line and column, and a fix hint.\n2. **Synthesizes and caches.** `cue_01` and `cue_03` go to ElevenLabs (with word timestamps); `cue_02` goes\n   to a local Kokoro-FastAPI server (`local_kokoro` is an alias of the built-in `kokoro` provider). Each\n   result is cached by a content hash, so later renders only pay for lines that changed.\n3. **Levels the voices.** Each cue is trimmed of leading and trailing silence and normalized to -20 LUFS,\n   so the cloud voice and the local voice sit at the same level.\n4. **Lays out the timeline in sample frames.** `cue_01` starts at 0. `cue_02` starts 150 ms after `cue_01`\n   ends. `cue_03` is sequential, 350 ms after `cue_01`, but that slot collides with `cue_02`, so the default\n   overlap policy (`push`) **automatically moves `cue_03` to 350 ms after `cue_02` ends**, keeping its pause\n   as the gap, and reports an info `cue_pushed`. The result is a natural three-turn exchange with no timing\n   arithmetic by the agent.\n5. **Places the music.** The bed is a looping clip without a length, so it runs to the end of the timeline\n   with a 1.2 s fade-in; the timeline ends 2.5 s after the last word (the bed's `fade_out_ms`) and the bed\n   fades out over exactly that tail. The whoosh fires at 0.\n6. **Ducks the bed** by 18 dB whenever either voice speaks. Detection runs on the voices' own signal with\n   an 80 ms lookahead (it defaults to the attack time), so the bed is already down when the first syllable\n   lands; it recovers over 350 ms, and the default 250 ms hold bridges the 150 ms turn gap.\n7. **Pans** the two voices to -0.15 and +0.15 (constant power).\n8. **Masters** the mix to -16 LUFS integrated with a -1.0 dBTP true-peak ceiling (from the default\n   `podcast` preset), iterating loudness gain and a 4× oversampled true-peak limiter until within 0.2 LU\n   (the closest pass is kept and `loudness_target_missed` says by how much when no pass gets there; an\n   unreachable target gives the loudest master within 6 dB of average limiting, whatever the target).\n9. **Encodes** a 48 kHz, 320 kbps MP3 natively, decodes and measures the delivered file (re-limiting if the\n   codec pushed a peak over the ceiling), and writes `timeline.vtt` and `timeline.srt` from the placed word\n   timings, labelled `narrator` and `guest`.\n\nBefore spending anything, `soniccraft plan` (alias `dry-run`) shows the same layout from estimated\ndurations without calling a TTS API (abridged JSON, estimated numbers):\n\n```json\n{ \"valid\": true, \"duration_ms\": 20616.9, \"estimated\": true,\n  \"items\": [\n    { \"id\": \"cue_01\", \"kind\": \"cue\", \"start_ms\": 0.0,     \"end_ms\": 4838.7,  \"duration_source\": \"estimate\" },\n    { \"id\": \"cue_02\", \"kind\": \"cue\", \"start_ms\": 4988.7,  \"end_ms\": 10863.7, \"duration_source\": \"estimate\" },\n    { \"id\": \"cue_03\", \"kind\": \"cue\", \"start_ms\": 11213.7, \"end_ms\": 18116.9, \"duration_source\": \"estimate\",\n      \"pushed_from_ms\": 5188.7 } ],\n  \"synthesis\": { \"cues\": 3, \"cached\": 0, \"characters_to_synthesize\": 276 } }\n```\n\n## Features\n\n- **Timeline DSL v1.0** in YAML or JSON with a published JSON Schema. Strict validation: unknown keys are\n  errors with did-you-mean hints; every problem is reported at once with a stable code, path, line/column\n  and a mechanical fix.\n- **Timing without arithmetic:** sequential cues and pauses, absolute starts, and relative anchors\n  (`after_cue`, `with_cue`, `before_cue`, with signed offsets) on cues, clips or whole tracks. Speech\n  overlaps are pushed apart automatically (or warned about, rejected, or allowed per cue for crosstalk).\n- **Eight TTS providers:** ElevenLabs, any OpenAI-compatible `/audio/speech` server, Cartesia, Kokoro\n  (Kokoro-FastAPI), Piper, macOS `say`, any local command, and a deterministic mock voice. Mix them freely\n  in one timeline. Speakers registry with layered voice settings.\n- **Cheap iteration:** a content-addressed TTS cache, dry runs that never call paid APIs (with estimated\n  durations and per-provider character counts), bounded concurrency and retries with backoff.\n- **Music and sound effects:** loops (seamless, or with loop crossfades), trims, fades with three curves,\n  crossfades between clips, open-ended beds that end with an automatic fade-out tail.\n- **Sidechain ducking** driven by the actual speech signal (or by the timeline), with attack, release,\n  hold and lookahead.\n- **Loudness done properly:** per-source BS.1770 normalization, then master normalization to a preset\n  (podcast -16, streaming -14, EBU R128 broadcast -23, ATSC A/85 -24 LUFS) and an oversampled true-peak\n  limiter. Lossy outputs are decoded and measured as delivered, with codec true-peak and loudness-drift\n  compensation.\n- **Native DSP and encoders:** wav, mp3 and flac with no ffmpeg; ogg, opus and m4a through ffmpeg when it is\n  installed. 16/24-bit integer or 32-bit float, fixed-seed TPDF dither.\n- **Subtitles from the rendered audio:** WebVTT, SRT, JSON with per-word timestamps, and a\n  HyperFrames-compatible transcript; speaker labels; per-cue caption overrides.\n- **Side files:** per-track stems, a deterministic manifest, and a self-contained HTML preview with the\n  waveform, ducking envelopes and captions.\n- **Agent surfaces:** machine-readable `--json` reports from `validate`, `plan`, `render`, `providers` and\n  `voices`, an MCP server that speaks both protocol eras, an Agent Skill, and a Claude Code plugin.\n\n## Quickstart\n\nInstall a prebuilt binary (checksum-verified; no Rust or C toolchain needed):\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/user-github-me/soniccraft/main/install.sh | sh   # macOS, Linux: ~/.local/bin\nbrew install user-github-me/soniccraft/soniccraft                                            # Homebrew (macOS, Linux)\n```\n\n```powershell\nirm https://raw.githubusercontent.com/user-github-me/soniccraft/main/install.ps1 | iex        # Windows\nscoop bucket add soniccraft https://github.com/user-github-me/scoop-soniccraft; scoop install soniccraft\n```\n\nWith Rust: `cargo binstall soniccraft` downloads the same release binary, and `cargo install --locked soniccraft`\nbuilds it from [crates.io](https://crates.io/crates/soniccraft) (requirements below).\n\n**From source.** Requirements: Rust 1.89 or newer ([rustup](https://rustup.rs)) and a C toolchain for the bundled LAME MP3\nencoder (`cc` and `make`: Xcode Command Line Tools on macOS, `build-essential` on Linux; on Windows, the\nVisual Studio Build Tools, plus `AWS_LC_SYS_PREBUILT_NASM=1` in the environment unless NASM is installed).\nffmpeg is optional (ogg/opus/m4a output, extra input formats). Prebuilt binaries need none of this: see\nbelow.\n\n```bash\ngit clone https://github.com/user-github-me/soniccraft\ncd soniccraft\ncargo install --locked --path crates/soniccraft-cli    # installs `soniccraft` and `soniccraft-mcp` into ~/.cargo/bin\nsoniccraft --version\n```\n\n`--locked` builds with the dependency versions in `Cargo.lock`, the ones CI tests.\n\n**Prebuilt binaries.** Each [GitHub release](https://github.com/user-github-me/soniccraft/releases) has archives for\nLinux (`x86_64-unknown-linux-gnu`, `aarch64-unknown-linux-gnu`; glibc 2.35 or newer), macOS\n(`aarch64-apple-darwin`, `x86_64-apple-darwin`; macOS 11 or newer) and Windows (`x86_64-pc-windows-msvc`).\n`soniccraft-v<version>-<target>.tar.gz` (`.zip` for Windows) holds `soniccraft`, `soniccraft-mcp`, the\nlicense texts and `THIRD-PARTY-LICENSES.txt`. The `-no-mp3` archives leave out the LGPL MP3 encoder; mp3\noutput then needs ffmpeg. Check a download with `sha256sum --check --ignore-missing SHA256SUMS` (on macOS:\n`shasum -a 256 --check --ignore-missing SHA256SUMS`). The binaries are not notarized: on macOS a browser\ndownload is quarantined; clear it with `xattr -d com.apple.quarantine soniccraft soniccraft-mcp` (curl and the\nnpm launcher do not set the attribute).\n\n**Container image.** Each release is also published as\n[`ghcr.io/user-github-me/soniccraft`](https://github.com/user-github-me/soniccraft/pkgs/container/soniccraft)\n(linux/amd64 and linux/arm64, with ffmpeg for ogg, opus and m4a output):\n\n```bash\ndocker run --rm -v \"$PWD\":/work ghcr.io/user-github-me/soniccraft render timeline.yaml\ndocker run -i --rm -v \"$PWD\":/work ghcr.io/user-github-me/soniccraft mcp --base-dir /work   # MCP on stdio\n```\n\nFiles written to the mounted directory belong to uid 1000; add `--user \"$(id -u):$(id -g)\"` to write them as\nyourself. Pass API keys with `-e ELEVENLABS_API_KEY` (never in the timeline). macOS `say` is not available in\nthe container: use the mock voice, a cloud provider or a local server. `docker build -t soniccraft .` builds the\nsame image from source.\n\nRender the demo with **zero API keys**:\n\n```bash\n# macOS: Samantha and Daniel from macOS `say`, plus the built-in mock voice\nsoniccraft render examples/offline-demo/timeline.yaml --preview\n\n# any OS: the same timeline with mock voices only\nsoniccraft render examples/offline-demo/timeline.mock.yaml\n```\n\nThe macOS run writes `examples/offline-demo/offline-demo.wav` with `.vtt`, `.srt`, `.words.json` and\n`.transcript.json` subtitles, a `.manifest.json`, and `offline-demo.preview.html` (open it in a browser to\nsee the waveform, the ducking envelope and the captions). The full loop an agent follows:\n\n```bash\nsoniccraft validate examples/offline-demo/timeline.mock.yaml          # errors, warnings, fix hints\nsoniccraft plan     examples/offline-demo/timeline.mock.yaml          # dry run: layout, ducking, cost\nsoniccraft render   examples/offline-demo/timeline.mock.yaml --json   # one JSON report on stdout\n```\n\nWith API keys and a local Kokoro server, render the README timeline:\n\n```bash\nexport ELEVENLABS_API_KEY=...                                          # never put keys in timelines\ndocker run -d -p 8880:8880 ghcr.io/remsky/kokoro-fastapi-cpu:latest\nsoniccraft render examples/tech-briefing/timeline.yaml\n```\n\nStart your own project from a template (writes the timeline and generated stand-ins for its audio assets):\n\n```bash\nsoniccraft init my-episode --template two-person-podcast\n```\n\n## CLI\n\n```\nsoniccraft validate <TIMELINE> [--json] [--check-credentials]\nsoniccraft plan <TIMELINE> [--json]                      (alias: dry-run)\nsoniccraft render <TIMELINE> [-o PATH] [--format F] [--subtitles] [--stems] [--manifest] [--preview]\n                  [--no-cache] [--cache-dir DIR] [--jobs N] [--json]\nsoniccraft schema [-o FILE]\nsoniccraft init [DIR] [--template NAME] [--list] [--no-assets] [--force]\nsoniccraft providers [--json]\nsoniccraft voices <PROVIDER> [--json]\nsoniccraft cache <info|clear> [--cache-dir DIR] [TIMELINE]\nsoniccraft mcp [--base-dir DIR] [--allow-command]\n```\n\n| Command | What it does |\n|---|---|\n| `validate` | Parses and checks the timeline and its environment (assets exist and decode, credentials present and allowed to go to their host, local TTS servers answer, macOS voices and piper models installed, ffmpeg when needed, the render fits the memory budget, cache status). `--check-credentials` also verifies API keys with a cheap authenticated request. |\n| `plan` / `dry-run` | Validates, then lays out the timeline with cached or estimated durations (mock voices exactly): item times, overlap moves, ducking regions, characters to synthesize per provider, and an ASCII diagram. Never calls a paid API. |\n| `render` | The full pipeline. `-o` sets the output path (its extension picks the format only when `output.format` is not set: for a `format: wav` timeline write `-o out.mp3 --format mp3`); `--format` is one of wav, mp3, flac, ogg, opus, m4a and overrides both. `--subtitles`, `--stems` and `--manifest` switch on those exports (like the `output.export_*` fields); `--preview` writes `<stem>.preview.html`. `--no-cache` ignores cached TTS audio (fresh results are still cached); `--cache-dir` picks the cache; `--jobs N` limits concurrent synthesis requests (default 4). Outputs are put in place only when the render succeeds, and existing files that are not SonicCraft outputs are never overwritten. |\n| `schema` | Prints the timeline JSON Schema, or writes it with `-o`. |\n| `init` | Writes `DIR/timeline.yaml` (default `DIR`: the current directory) from a template, plus the audio assets it references, generated procedurally (a seamless synth loop and three SFX, deterministic and royalty-free). Templates (`--list`): `offline-demo` (default on macOS: system voices + mock), `offline-demo-mock` (default elsewhere: renders anywhere), `tech-briefing`, `two-person-podcast`, `voiceover-with-music`, `dialogue-crosstalk`. `--no-assets` writes only the timeline; existing files are never overwritten without `--force`. |\n| `providers` | Lists built-in providers with availability, credential status and default voices. |\n| `voices` | Lists the voices of one provider (may call its API); the provider name ignores case and `-`/`_`. |\n| `cache` | `info` shows the TTS cache location, entry count and size; `clear` empties it. |\n| `mcp` | Runs the MCP server on stdio. `--allow-command` (or `SONICCRAFT_ALLOW_COMMAND=1`) lets timelines run `command` providers, which are refused by default. |\n\nExit codes: `0` success, `1` timeline invalid (error diagnostics), `2` usage error, `3` runtime or render\nfailure, `130` interrupted. With `--json`, stdout contains exactly one JSON document (the report, or\n`{\"message\", \"diagnostics\", \"hint\"}` when the command fails, usage errors included); progress and logs go to\nstderr. A render stopped by its environment or options (a missing key, a provider that cannot run, a voice\nthat is not installed, the memory budget) also exits 1 and ends with `error: render stopped: N errors`.\nTimelines (files, FIFOs, `-` for stdin) are read up to the 5 MB document limit and no further. Run\n`soniccraft <command> --help` for every option.\n\n## MCP server\n\n`soniccraft mcp` serves the engine over stdio (newline-delimited JSON-RPC 2.0); `soniccraft-mcp` is the\nsame server as a zero-argument binary. It supports both MCP protocol eras: the `initialize` handshake\n(2024-11-05 through 2025-11-25, used by Codex and Cursor) and the stateless 2026-07-28 revision with\n`server/discover` (which Claude Code probes first).\n\n| Tool | Input | Result |\n|---|---|---|\n| `soniccraft_validate_timeline` | `{yaml_content?, yaml_path?, base_dir?, check_credentials?, detail?}` (one of content/path; `path` is an alias of `yaml_path`) | ValidationReport |\n| `soniccraft_dry_run` | `{yaml_content?, yaml_path?, base_dir?, detail?}` | PlanReport (+ diagram as text) |\n| `soniccraft_render` | `{yaml_content?, yaml_path?, output_path?, format?, base_dir?, export_subtitles?, no_cache?, detail?}` | RenderReport |\n| `soniccraft_get_schema` | `{detail?}` | DSL quick reference (+ the JSON Schema with `detail: \"full\"`) |\n| `soniccraft_list_providers` | `{}` | Providers, availability (local servers probed), credential status, default voices |\n| `soniccraft_list_voices` | `{provider}` | Voices (may call the provider API) |\n\nReports are returned as `structuredContent` (mirrored as JSON text, plus a short summary). Long timelines\nget a compact report by default (`detail: \"auto\"`) so the result stays readable in an agent's context; pass\n`detail: \"full\"` for every item. Renders add `resource_link` blocks with `file://` URIs for the audio and side\nfiles (sessions on protocol revisions before 2025-06-18 get the files as text instead); audio is never inlined\nas base64. `format` overrides the timeline's `output.format` (pass it with an `output_path` whose extension\nnames another format). For mp3/ogg/opus/m4a, the report's loudness describes the delivered file\n(`loudness.pre_encode` has the master's). An invalid timeline is a successful validate/dry-run call\n(`valid: false`); a failed render or a bad argument returns `isError: true` with what to fix, so the agent\ncan repair and retry. Relative paths resolve against `base_dir`, else `soniccraft mcp --base-dir`, else\n`CLAUDE_PROJECT_DIR`, else the server's working directory. Long renders report `notifications/progress` and\nhonour cancellation; at most two run at once (`SONICCRAFT_MCP_MAX_RENDERS`). Each render may use up to\n`SONICCRAFT_MAX_RENDER_GB` of memory (default 8 GB), so the server's peak can reach their product (16 GB by\ndefault); lower either on small machines. Timelines run `command` providers only when the server is started\nwith `soniccraft mcp --allow-command` (see [Security](#security)). Tool arguments and report shapes:\n[skills/soniccraft/reference/mcp-tools.md](skills/soniccraft/reference/mcp-tools.md).\n\n**Claude Code**\n\n```bash\nclaude mcp add soniccraft -- soniccraft mcp                    # local scope (default): this project, private to you\nclaude mcp add --scope user soniccraft -- soniccraft mcp       # all your projects\nclaude mcp add --scope project soniccraft -- soniccraft mcp    # writes .mcp.json to commit with the project\nclaude mcp add soniccraft -- soniccraft mcp --allow-command    # only if your timelines use `command` providers (they run local programs)\n```\n\nOr in a project's `.mcp.json` (`timeout` is the per-call limit in milliseconds). A stdio server inherits the\nenvironment Claude Code runs in, so exported API keys reach it without an `env` block:\n\n```json\n{\n  \"mcpServers\": {\n    \"soniccraft\": {\n      \"type\": \"stdio\",\n      \"command\": \"soniccraft\",\n      \"args\": [\"mcp\"],\n      \"timeout\": 900000\n    }\n  }\n}\n```\n\nDo not add `\"env\": {\"ELEVENLABS_API_KEY\": \"${ELEVENLABS_API_KEY}\"}`: when the variable is unset, the client\npasses the literal text `${ELEVENLABS_API_KEY}`. SonicCraft treats such a placeholder as a missing key and\nsays so (`missing_credentials`); if you need an `env` entry, write `\"${ELEVENLABS_API_KEY:-}\"`.\n\n**Without installing Rust**, through npm (Node.js 20 or newer): the [`soniccraft-mcp`](packages/soniccraft-mcp)\npackage uses `soniccraft` from `PATH` when there is one; otherwise it downloads the prebuilt release for your\nplatform on first use and verifies it against SHA-256 checksums pinned in the package. Each version on npm is\nthe release's `soniccraft-mcp-X.Y.Z.tgz` asset, published from GitHub Actions with\n[provenance](https://docs.npmjs.com/generating-provenance-statements).\n\n```bash\nclaude mcp add soniccraft -- npx -y soniccraft-mcp\n```\n\n```json\n{ \"mcpServers\": { \"soniccraft\": { \"command\": \"npx\", \"args\": [\"-y\", \"soniccraft-mcp\"] } } }\n```\n\nArguments after the package name go to `soniccraft mcp` (for example `--base-dir`);\n[packages/soniccraft-mcp/README.md](packages/soniccraft-mcp/README.md) covers platforms, environment\nvariables, proxies and offline installs. The same `npx` form works in the Cursor and Codex configurations\nbelow (command `npx`, args `[\"-y\", \"soniccraft-mcp\"]`).\n\n**Cursor**: `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (project):\n\n```json\n{\n  \"mcpServers\": {\n    \"soniccraft\": {\n      \"type\": \"stdio\",\n      \"command\": \"soniccraft\",\n      \"args\": [\"mcp\"],\n      \"env\": { \"ELEVENLABS_API_KEY\": \"${env:ELEVENLABS_API_KEY}\" }\n    }\n  }\n}\n```\n\nIn a project-level file you can add `\"CLAUDE_PROJECT_DIR\": \"${workspaceFolder}\"` to `env` so relative paths\nin tool calls resolve against the workspace. If a key variable is unset and the client passes the\nplaceholder text through, SonicCraft reports the key as missing instead of sending the placeholder.\n\n**Codex**\n\n```bash\ncodex mcp add soniccraft -- soniccraft mcp\n```\n\nThen raise the tool timeout in `~/.codex/config.toml` (Codex defaults to 60 s; renders can take longer),\nforward the API keys you use, and decide how the render tool is approved:\n\n```toml\n[mcp_servers.soniccraft]\ncommand = \"soniccraft\"\nargs = [\"mcp\"]\nenv_vars = [\"ELEVENLABS_API_KEY\", \"OPENAI_API_KEY\", \"CARTESIA_API_KEY\"]\nstartup_timeout_sec = 20\ntool_timeout_sec = 900\ndefault_tools_approval_mode = \"approve\"   # needed for non-interactive `codex exec`, see below\n```\n\n`soniccraft_render` writes files, so Codex asks before running it. Interactive sessions prompt for approval;\nnon-interactive `codex exec` (approval policy `never`) fails the call with \"MCP tool call requires approval\"\nunless the server entry approves it: `default_tools_approval_mode = \"approve\"` for every SonicCraft tool, or\nonly the render tool with\n\n```toml\n[mcp_servers.soniccraft.tools.soniccraft_render]\napproval_mode = \"approve\"\n```\n\nFor a single run: `codex exec -c 'mcp_servers.soniccraft.default_tools_approval_mode=\"approve\"' ...`.\n\n## Agent Skill\n\n[`skills/soniccraft/`](skills/soniccraft/SKILL.md) is an [Agent Skills](https://agentskills.io) package:\n`SKILL.md` (workflow, DSL essentials, duration heuristics, mixing practice, a diagnostics fix table) plus\non-demand references (full DSL reference, providers, mixing guide, every diagnostic code, MCP tools) and\nfive templates. It is written around the CLI, so it works in any agent, and mentions the MCP tools as an\nalternative.\n\n```bash\nnpx skills add user-github-me/soniccraft                                              # pick agents interactively\nnpx skills add user-github-me/soniccraft --skill soniccraft -a claude-code -a codex -a cursor -y   # project scope\nnpx skills add user-github-me/soniccraft --skill soniccraft -g -a claude-code -y                   # global\n```\n\nOr copy the directory by hand: `cp -R skills/soniccraft ~/.claude/skills/` (Claude Code, user),\n`.claude/skills/soniccraft` (Claude Code, project), `~/.agents/skills/soniccraft` or\n`.agents/skills/soniccraft` (Codex, Cursor and other agents that read `.agents/skills`).\n\n**Claude Code plugin** (the skill plus the MCP server in one step; needs `soniccraft` on PATH):\n\n```bash\nclaude plugin marketplace add user-github-me/soniccraft\nclaude plugin install soniccraft@soniccraft\n```\n\nIf the plugin's MCP server shows as failed, the `soniccraft` binary is usually not on PATH: install it (see\n[Quickstart](#quickstart)) and restart Claude Code. The skill's Setup section tells agents the same.\n\nFor Codex, `skills/soniccraft/agents/openai.yaml` declares the MCP dependency, launched as `soniccraft-mcp`.\n\n## Providers\n\n| Provider | `provider:` | Credentials / setup | Default voice | Word timings |\n|---|---|---|---|---|\n| ElevenLabs | `elevenlabs` | `ELEVENLABS_API_KEY` | none (`voice_id` required) | yes |\n| OpenAI-compatible | `openai` | `OPENAI_API_KEY`; `base_url` for other servers | `alloy` | estimated |\n| Cartesia | `cartesia` | `CARTESIA_API_KEY` | none (`voice_id` required) | yes |\n| Kokoro (Kokoro-FastAPI) | `kokoro` | local server: `KOKORO_BASE_URL` (default `http://localhost:8880`), optional `KOKORO_API_KEY` | `af_heart` | yes |\n| Piper | `piper` | `pip install piper-tts`; voice models (`voices_dir`, `PIPER_VOICES_DIR`, or next to the timeline) | none (`voice_id` required) | estimated |\n| macOS speech | `macos_say` | macOS | `Samantha` | estimated |\n| Custom command | `command` | your argv template | none (`voice_id` required) | estimated |\n| Mock | `mock` | nothing (deterministic synthetic voice) | `mock` | exact |\n\n- ElevenLabs audio is requested as raw `pcm_24000` by default (falls back to it if a configured format is\n  not available on your plan); `eleven_v4` accepts only `stability` and `similarity_boost`.\n- `openai` is a generic OpenAI-compatible `/audio/speech` client. OpenAI's own TTS models retire on\n  2027-01-06; point `base_url` at Kokoro-FastAPI, Speaches, LocalAI or another compatible server.\n- Cartesia uses API version `2026-08-14` and defaults to `sonic-3.6`.\n- Kokoro: `docker run -p 8880:8880 ghcr.io/remsky/kokoro-fastapi-cpu:latest`; word timestamps come from its\n  captioned-speech endpoint. `soniccraft providers` and `validate` probe local servers (Kokoro-FastAPI,\n  OpenAI-compatible servers on this machine or a private network) with a 300 ms TCP connect; one that does\n  not answer is not available (`no Kokoro-FastAPI server answered at http://localhost:8880`) and validate\n  reports `provider_unavailable`.\n- Piper is maintained as OHF-Voice/piper1-gpl: `pip install piper-tts`, then download a voice into the\n  project: `python3 -m piper.download_voices en_US-lessac-medium --data-dir voices` and set\n  `voices_dir: voices` on the provider (or download it next to the timeline). `voice_id: en_US-lessac-medium`\n  is looked up in `voices_dir`, `$PIPER_VOICES_DIR`, the timeline directory and\n  `~/.local/share/piper/voices`, in that order; `soniccraft voices piper` lists what it finds, or the\n  searched directories and the download command, and validate reports a missing model as `unknown_voice`.\n- macOS `say` silently falls back to the default voice for unknown names, so SonicCraft checks voice names\n  against `say -v '?'` first (`unknown_voice` in validate, with the closest installed names).\n- API keys are sent only to the provider's own API host, to servers on this machine (only the provider's own\n  key variable, such as `OPENAI_API_KEY`), or to hosts listed in `SONICCRAFT_ALLOWED_HOSTS`; a key that would go\n  anywhere else is refused before any request (`provider_unavailable`). See [Security](#security).\n\nSetup, models, voices, settings and limits per provider:\n[skills/soniccraft/reference/providers.md](skills/soniccraft/reference/providers.md).\n\n| Environment variable | Purpose |\n|---|---|\n| `ELEVENLABS_API_KEY`, `OPENAI_API_KEY`, `CARTESIA_API_KEY` | Provider API keys (rename per instance with `api_key_env`). |\n| `KOKORO_BASE_URL`, `KOKORO_API_KEY` | Kokoro-FastAPI location and optional key. |\n| `PIPER_VOICES_DIR` | Directory searched for `<voice_id>.onnx` (after the provider's `voices_dir`). |\n| `SONICCRAFT_ALLOWED_HOSTS` | More hosts that may receive API keys (comma-separated, `*.example.com` patterns), besides each provider's own API host and servers on this machine (which get only the provider's own key variable). |\n| `SONICCRAFT_CACHE_DIR` | TTS cache location (overridden by `--cache-dir`). |\n| `SONICCRAFT_MAX_RENDER_GB` | Memory budget of one render in GB (default 8); a timeline that needs more fails with `timeline_too_long` before any TTS request. |\n| `CLAUDE_PROJECT_DIR` | MCP server: base directory for relative paths when a call has no `base_dir`. |\n| `SONICCRAFT_ALLOW_COMMAND` | MCP server: `1` lets timelines run local programs (`command` providers, a piper `command`); off by default, like `soniccraft mcp --allow-command`. |\n| `SONICCRAFT_MCP_MAX_RENDERS` | MCP server: renders that run at once (default 2); later ones wait. |\n\n## Subtitles and HyperFrames\n\n`output.export_subtitles` takes `true` (= vtt + srt) or a list of `vtt`, `srt`, `json` and `transcript`.\n`json` (`<stem>.words.json`) holds the captions and every word's timing; `transcript`\n(`<stem>.transcript.json`) is a word array in seconds in the shape HyperFrames' `transcribe` command\nconsumes. To caption a [HyperFrames](https://github.com/heygen-com/hyperframes) video with a SonicCraft\nvoiceover:\n\n```bash\nsoniccraft render voiceover.yaml            # with output.export_subtitles: [vtt, transcript]\nnpx hyperframes transcribe voiceover.transcript.json --dir my-video\n```\n\nand play the mastered audio in the composition from the start, e.g.\n`<audio id=\"voiceover\" src=\"assets/voiceover.wav\" data-start=\"0\" data-duration=\"21.04\"></audio>`.\n\n## Determinism and caching\n\n- **Deterministic output.** The same timeline, TTS cache and binary produce byte-identical audio,\n  subtitles and manifests. No wall-clock timestamps, random seeds, host names or absolute paths leak into\n  rendered artifacts; dither and the mock voice use fixed seeds; all geometry is integer sample frames.\n  Stage timings and cache statistics appear only in the live report, never in the manifest, so a render\n  from a warm cache writes the same manifest as a fresh one.\n- **Content-addressed TTS cache.** Each cue's key is a blake3 hash of a canonical JSON of everything that\n  affects the audio (provider type, API version, non-default base URL, model, voice, language, text,\n  settings, output format; for command/piper the templates, the program and a hash of its contents, the\n  files its arguments name and the piper model). Entries are `<key>.wav` (32-bit float, the provider's\n  native rate, untrimmed) and `<key>.json` (metadata and word timings), written atomically. Changing one\n  line re-synthesizes one cue; retiming, re-mixing or re-mastering costs nothing. A cache that cannot be\n  written gives the warning `cache_write_failed`; the render still succeeds.\n- **Location:** `--cache-dir` > `SONICCRAFT_CACHE_DIR` > `<timeline dir>/.soniccraft/cache`.\n  `soniccraft cache info` and `soniccraft cache clear` manage it; `render --no-cache` re-synthesizes but\n  still refreshes the cache.\n- **Free dry runs.** `plan` uses cached durations where available, renders mock voices in-process (exact)\n  and estimates the rest from per-provider speaking rates (characters per second); it never calls a\n  provider.\n\n## Security\n\nA timeline is data, often written by an agent from text it found somewhere, so SonicCraft treats it as\nuntrusted:\n\n- **Secrets never live in timelines.** API keys, tokens and passwords in a timeline (a key named\n  `api_key`/`token`/`secret`/`password`, an `sk-...` value, a password in a URL, a key inside a provider's\n  `command`) are the error `secret_in_timeline`, and diagnostics never repeat them (CLI snippets show\n  `<redacted>`). Keys come only from environment variables (`api_key_env`).\n- **Keys go only where they belong:** to each provider's own API host, to servers on this machine (only\n  the provider's own key variable: `OPENAI_API_KEY` for an `openai` instance pointed at a local server,\n  `KOKORO_API_KEY` for Kokoro), or to hosts you list in `SONICCRAFT_ALLOWED_HOSTS` (in soniccraft's or the\n  MCP server's environment, never in the timeline). A timeline cannot send a variable of its choosing\n  (`api_key_env: GITHUB_TOKEN`) to a local server, or any key to another device on the local network, unless\n  that host is listed. To use a key with an OpenAI-compatible server elsewhere on your network or remote, a\n  remote Kokoro or a regional endpoint, set for example `SONICCRAFT_ALLOWED_HOSTS=tts.example.com`. Anything\n  else is refused before a request is made. Redirects are not followed, and keys echoed by a server are removed from\n  messages.\n- **Local programs:** `command` providers (and piper with its own `command`) run programs. Over MCP the\n  server refuses them unless started with `soniccraft mcp --allow-command` (or `SONICCRAFT_ALLOW_COMMAND=1`).\n  Programs never see provider API keys, run in their own process group (killed as a whole on timeout or\n  cancel), and get the cue text through a file or stdin: an argument that would start with `-` is refused.\n- **Files:** outputs are written to temporary files and put in place only when the whole render succeeds;\n  a failed or cancelled render leaves earlier outputs untouched. An existing file is replaced only by an\n  output of its kind (`-o notes.txt` over a text file is refused). Over MCP, a timeline's own `output.path`\n  must stay inside the base directory (an explicit `output_path` argument may point anywhere).\n- **Reads:** timelines are at most 5 MB, and `/dev/zero`, FIFOs or endless stdin fail at once.\n\n## Resources\n\nA render holds the mastered mix in memory: tracks are mixed in windows straight into one full-length master,\nstems stream to their files and mastering works in place. Measured peaks (stereo, 48 kHz): about 23 MB per\nminute of output for WAV, MP3, ogg, opus and m4a when music dominates (a 10-minute bed: 245 MB), about 40 MB\nper minute when speech runs throughout (a 16-minute voiceover: 615 MB), and about 85 MB per minute for\n24-bit FLAC, whose encoder keeps the whole stream (10 minutes: 870 MB). `SONICCRAFT_MAX_RENDER_GB` (default\n8) caps a render: one that would need more fails with `timeline_too_long` before any TTS request, and\nvalidate/plan warn ahead. Timelines are at most 24 h long.\n\n## Architecture\n\n```\n  timeline.yaml / .json\n          |\n          v\n  +----------------+   diagnostics: code, path, line:col, hint\n  | parse + lower  |-------------------------------------------------+\n  | (core)         |                                                 |\n  +-------+--------+                                                 |\n          | IR (Program): resolved voices, defaults, anchors          |\n          v                                                          |\n  +----------------+  cached / estimated durations  +-------------+   |\n  | synthesis      |------------------------------->|  layout     |   |\n  | (tts + cache)  |  exact durations (render)      |  (frames)   |   |\n  +-------+--------+                                +------+------+   |\n          | audio + word timings                          | placements|\n          v                                               v           v\n  +------------------------------------------------------------+  reports:\n  | DSP (dsp): decode -> resample -> trim -> normalize -> clips |  validate,\n  | -> pan -> stems -> ducking -> master (R128 + true peak)     |  plan,\n  +-------+----------------------------------------------------+  render\n          |\n          v\n  encode (wav/mp3/flac native; ogg/opus/m4a via ffmpeg) + captions (vtt/srt/json/transcript)\n  + stems + manifest + preview.html\n```\n\n| Crate / directory | Responsibility |\n|---|---|\n| [`soniccraft-core`](crates/soniccraft-core) | DSL raw AST (serde + schemars), YAML/JSON parsing with source spans, diagnostics, validation and lowering to the IR, JSON Schema generation, static provider metadata. Pure: no I/O beyond environment variables. |\n| [`soniccraft-layout`](crates/soniccraft-layout) | Timeline geometry in sample frames: scheduling, the speech-overlap policy, clip lengths, content end and tail; the ASCII plan diagram. Pure. |\n| [`soniccraft-captions`](crates/soniccraft-captions) | Word alignment (provider timings or estimates), caption segmentation, WebVTT/SRT/JSON/transcript writers. |\n| [`soniccraft-dsp`](crates/soniccraft-dsp) | AudioBuffer, decoding (symphonia; ffmpeg fallback), resampling (rubato), analysis, clip rendering, pan, ducking, loudness (ebur128), true-peak limiter, mastering loop, encoders (WAV, LAME MP3, FLAC; ffmpeg for ogg/opus/m4a). |\n| [`soniccraft-tts`](crates/soniccraft-tts) | Provider trait and the eight built-in providers, content-addressed cache, duration estimator, retries and concurrency, credential checks, voice listing. |\n| [`soniccraft-engine`](crates/soniccraft-engine) | Orchestration: `validate()`, `plan()` (dry run), `render()`; environment checks; all report types. |\n| [`soniccraft-mcp`](crates/soniccraft-mcp) | MCP stdio server exposing the engine as tools. |\n| [`soniccraft`](crates/soniccraft-cli) (`crates/soniccraft-cli`) | The `soniccraft` binary (and the `soniccraft-mcp` launcher). Human-readable and `--json` output. |\n| [`packages/soniccraft-web-preview`](packages/soniccraft-web-preview) | Zero-dependency WebAudio visualizer; the engine embeds a vendored copy (`crates/soniccraft-engine/assets/preview.html`) for `render --preview`. |\n| [`packages/soniccraft-mcp`](packages/soniccraft-mcp) | The npm launcher (`npx -y soniccraft-mcp`): finds or downloads the release binary and runs `soniccraft mcp`. |\n| [`skills/soniccraft`](skills/soniccraft) | The Agent Skill: `SKILL.md`, references, templates. |\n| [`examples`](examples) | Runnable timelines with royalty-free, procedurally generated assets. |\n\nDependencies point one way: `layout` → `core`; `tts` → {`core`, `dsp`}; `engine` → {`core`, `layout`,\n`captions`, `dsp`, `tts`}; `mcp` → `engine`; `cli` → {`engine`, `mcp`}. The full specification is\n[docs/DESIGN.md](docs/DESIGN.md); a contributor's tour of the pipeline is\n[docs/architecture.md](docs/architecture.md).\n\n## Roadmap\n\n- **Phase 1: DSL and compiler** (implemented in v0.1)\n  - [x] Timeline DSL v1.0 (YAML/JSON), JSON Schema, strict validation with did-you-mean hints\n  - [x] Lowering to a resolved IR: voices, providers, defaults, anchors, dependency checks\n  - [x] Sample-accurate layout with the speech-overlap policy, dry-run plans and ASCII diagrams\n- **Phase 2: Synthesis** (implemented in v0.1)\n  - [x] Eight providers: ElevenLabs, OpenAI-compatible, Cartesia, Kokoro, Piper, macOS say, command, mock\n  - [x] Content-addressed cache, duration estimates, concurrency limits, retries, credential checks\n- **Phase 3: Native DSP and mastering** (implemented in v0.1)\n  - [x] Decode, resample, silence trim, source normalization, loops, fades, crossfades, pan\n  - [x] Sidechain and cue-driven ducking, EBU R128 mastering loop, oversampled true-peak limiter\n  - [x] WAV/MP3/FLAC encoders; ogg/opus/m4a through ffmpeg\n- **Phase 4: Agent surfaces and outputs** (implemented in v0.1)\n  - [x] WebVTT, SRT, JSON word timestamps and HyperFrames transcripts\n  - [x] CLI with `--json`, MCP server (both protocol eras), Agent Skill, Claude Code plugin\n  - [x] Stems, manifest, web preview, examples with generated assets\n- **Phase 5: Distribution** (v0.1)\n  - [x] Release workflow: prebuilt binaries for macOS, Linux and Windows (with and without the LGPL MP3\n    encoder), `SHA256SUMS`, third-party license notices and the `soniccraft-mcp` npm launcher\n  - [x] Package managers: one-line installers (`install.sh`, `install.ps1`), a\n    [Homebrew tap](https://github.com/user-github-me/homebrew-soniccraft), a\n    [Scoop bucket](https://github.com/user-github-me/scoop-soniccraft) and the container image\n  - [x] Listings under the project's GitHub owner: the official MCP Registry\n    (`io.github.user-github-me/soniccraft`), the Claude Code plugin marketplace in this repository and the\n    Agent Skill for `npx skills add`\n  - [x] crates.io: `cargo install soniccraft` and `cargo binstall soniccraft`\n  - [x] npm: `npx -y soniccraft-mcp` (the MCP server, with the release binary downloaded and verified)\n  - [ ] Curated directories: the Anthropic plugin directory and the Cursor Marketplace\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md). The behaviour of every component is specified in\n[docs/DESIGN.md](docs/DESIGN.md); change the spec and the code together.\n\n## License\n\nSonicCraft is licensed under either of [Apache License, Version 2.0](LICENSE-APACHE) or\n[MIT license](LICENSE-MIT), at your option. Unless you explicitly state otherwise, any contribution\nintentionally submitted for inclusion in this project, as defined in the Apache-2.0 license, shall be\ndual licensed as above, without any additional terms or conditions.\n\nNative MP3 encoding uses LAME 3.100 (LGPL-2.0-or-later), statically linked through the `mp3lame-sys` and\n`mp3lame-encoder` crates (LGPL-3.0), behind the default-on cargo feature `mp3`. Build with\n`--no-default-features` to exclude it (for example\n`cargo install --locked --path crates/soniccraft-cli --no-default-features`); mp3 output then goes through\nffmpeg when available (`format_requires_ffmpeg` otherwise). The `-no-mp3` release archives are built that\nway and contain no LGPL code. The other release archives carry the license texts in `licenses/`, and\n[licenses/README.md](licenses/README.md) explains how to rebuild soniccraft with a modified LAME. Audio\ndecoding uses symphonia, and `dirs` uses option-ext, both MPL-2.0 (a file-level copyleft; used unmodified,\nwith their source on crates.io). Every other dependency is under a permissive license (MIT, Apache-2.0,\nBSD-3-Clause, ISC, Zlib, Unicode-3.0). Each release archive lists every crate compiled into it, with the\nlicense texts the crate ships, in `THIRD-PARTY-LICENSES.txt`, and CI enforces this policy with cargo-deny\n([deny.toml](deny.toml)).\n\nThe example audio in `examples/*/assets/` is synthesized from scratch by\n[`scripts/generate-example-assets.sh`](scripts/generate-example-assets.sh) and is covered by the same\nlicenses.\n"}