oh-my-cassette
This server currently exposes only a connection-status tool, not the Cassette editing tools.
Check whether the Cassette backend is reachable and compatible.
Briefly wait for a reconnect.
See why editing tools are unavailable while the backend is disconnected or refusing the token.
You cannot import media, edit video, ask editing questions, undo, or export until the backend is connected and its tools are listed.
One Claude Code session, typed as you see it. Five 4K travel films (10½ minutes of footage) and a song go in, a
four-sentence brief and a one-line revision go to the Cassette agent word for word, and a 30-second 1080p MP4 lands in
cassette-exports/. Waits are fast-forwarded: in real time the import took 4½ minutes, the first cut 9½, the revision
3½ and the export 1.
Cut a 30-second teaser for Turkey from these five films, on the beat of Pamgaea.mp3. Open on a hot-air balloon lighting up at dawn in Cappadocia, travel from the ancient ruins to the sea, name each place when it first appears, and end on the misty lake with the title TURKEY.
The ruins part drags. Keep only the two best Ephesus shots, and put the camel right after the balloons.
Install in 30 seconds
You need uv (it fetches Python and the package on first start) and ffmpeg to import video.
# Claude Code
claude plugin marketplace add Cassette-Editor/oh-my-cassette
claude plugin install oh-my-cassette@cassette-editor# Codex
codex plugin marketplace add https://github.com/Cassette-Editor/oh-my-cassette.git
codex plugin add oh-my-cassette@cassette-editor# Hermes
hermes mcp add cassette --command uvx --args oh-my-cassette==0.6.0OpenCode and any other MCP host: see Install below. Run uvx oh-my-cassette==0.6.0 login and complete the
email-code sign-in in your browser. Then restart your agent if it cannot refresh its tool list, and say:
Start a new Cassette project and import the clips in ./footage, then cut a 30-second travel video with a title at the start.
Related MCP server: oh-my-cassette
Upgrading from 0.4
0.5 is a rewrite. The 0.4 plugin carried its own editing runtime, music search and Hermes gateway; 0.5 is a thin bridge to the Cassette MCP service, and the tools your agent sees are the service's.
Sign in again with
uvx oh-my-cassette==0.6.0 login. 0.4 passwords and tokens are not used.Tools are new. Projects, editing turns, questions, plans, undo, media, analysis and export come from the service, with their own descriptions; the 0.4 tool names (
cassette_run_job,cassette_ingest_media,cassette_match_bgm, …) are gone. Reinstall the plugin, or point your host config atuvx oh-my-cassette==0.6.0.Projects live in your Cassette account, the same ones you open on the web or in Cassette for Mac, and the editor link each answer carries opens the project you are working on. 0.4 sessions do not carry over.
Hermes is a plain MCP host now; remove the 0.4 gateway plugin.
Projects and retention
A project the bridge starts is a cloud project that Cassette deletes, with its media, after three idle days; every use
pushes that back, and each answer says when it is due (retention_expires_at). Exported files are already on your
machine in cassette-exports/ and stay there. A project made in Cassette for Mac is kept, and the bridge can open, edit
and export it while that Mac is online, but its media are imported in the app.
Overview
Oh My Cassette connects your coding agent to the Cassette editing agent. It is a local stdio MCP server
that relays to the Cassette MCP service (CASSETTE_MCP_URL), which in turn calls the Cassette backend:
Claude Code / Codex / OpenCode / Hermes ──stdio──▶ oh-my-cassette ──HTTP──▶ Cassette MCP service ──▶ Cassette backendThe tools are the service's. Whatever it lists (projects, editing turns, questions, undo, export) is what your agent sees, with the service's own descriptions and workflow instructions. When Cassette changes, the tools change with it; the plugin does not need a release.
The bridge adds only what needs your machine. Local files named in a call are prepared and uploaded first (only from the workspace, media types only): video is transcoded with ffmpeg into the renditions the service declares, and audio or images it does not take as they are get converted. Each file uploads while the next one is prepared, in parts, four transfers at a time; a file whose upload was interrupted picks up its earlier import when you import it again. Exported files are downloaded into
cassette-exports/. The workspace identity travels with each call so the service can remember a project per directory.Long work never times out the host. Every call waits a bounded time; slow local preparation answers
preparingand keeps going in the background, and the next identical call picks it up.It survives deploys. It reconnects on its own, sends a call that names its request again when the connection drops or the service turns it away, resumes interrupted downloads, reports
bridge.*errors with aretryableflag (andretry_after_secondswhen the service said when), and, while the service is unreachable or refuses the token, lists a singlecassette_bridge_statustool that says why.
The rules between the plugin and the service are defined in
src/oh_my_cassette/contract.py; oh-my-cassette check verifies a
service against them.
Requirements
An invited Cassette account and a Cassette MCP service that implements contract v1. Check it with
uvx oh-my-cassette==0.6.0 check --url <endpoint>. While the service is unreachable or refuses the token, onlycassette_bridge_statusis listed.uv 0.5 or newer.
uvx oh-my-cassette==0.6.0resolves Python 3.11 or newer by itself.ffmpeg (with ffprobe) to import video:
brew install ffmpeg,sudo apt install ffmpegorwinget install ffmpeg. Video is transcoded on your machine before it uploads. A build withzscalealso tone-maps HDR footage properly.
Which formats a tool accepts, and how files are prepared before upload, is declared by the service (video, audio and image types at most).
Configuration
Sign in once with oh-my-cassette login, using the Cassette account of your projects: the browser asks for the emailed code and your consent. One sign-in reaches every project of the account, whether it was made on the web or in Cassette for Mac. A project whose media lie on a Mac opens in the app on that Mac; reading its pictures or sound and exporting it need that Mac online, and its media are imported there. Login, status and logout use your operating-system keyring; no token needs to be copied. Environment variables below are optional overrides.
Variable | Default | Meaning |
| the saved connection, otherwise | Explicit MCP endpoint override. |
| unset | Advanced bearer-token override. Normally use |
| the directory the host starts the server in | Where relative paths resolve, where local files may come from, and the directory the service may remember a project for. A host that starts the server in the whole disk or your home folder gives it no files to read until you set this, or |
| unset | Extra directories local files may come from ( |
|
| Where exported files are saved. The default must stay inside the workspace: a |
|
| Size limits per file. |
| off | Allow non-media uploads when a tool accepts them. |
|
| How long one call waits for local preparation and upload before it answers |
| from | The ffmpeg and ffprobe programs used to prepare local media. |
| the system temp directory | Where each upload makes its own private directory ( |
|
| How long to wait for the service when connecting. |
| unset | The host name sent to the service when the host does not identify itself. |
|
| Log level on stderr. |
Install
Claude Code
Plugin (recommended): the two commands at the top. The plugin uses the connection saved by oh-my-cassette login and starts uvx oh-my-cassette==0.6.0 with a 10-minute tool timeout.
Project scope without the plugin: copy .mcp.json into your project, or run:
claude mcp add --transport stdio cassette -- uvx oh-my-cassette==0.6.0The skill lives in skills/cassette-video-edit/SKILL.md and ships with the plugin.
Codex
Plugin: the two commands at the top (.codex-plugin/plugin.json declares the server inline with tool_timeout_sec: 600 and passes the CASSETTE_* variables through from your shell). Or add the server directly:
codex mcp add cassette -- uvx oh-my-cassette==0.6.0OpenCode
Add to opencode.json (project or ~/.config/opencode/opencode.json):
{
"mcp": {
"cassette": {
"type": "local",
"command": ["uvx", "oh-my-cassette==0.6.0"],
"timeout": 600000
}
}
}Then copy the skill so OpenCode loads it:
mkdir -p ~/.config/opencode/skills/cassette-video-edit
curl -fsSL https://raw.githubusercontent.com/Cassette-Editor/oh-my-cassette/v0.6.0/skills/cassette-video-edit/SKILL.md \
-o ~/.config/opencode/skills/cassette-video-edit/SKILL.mdOpenCode has no MCP elicitation, which is fine: the editing agent's questions come back as ordinary tool results, and the service's tools say how to answer them.
Hermes
hermes mcp add cassette --command uvx --args oh-my-cassette==0.6.0
mkdir -p ~/.hermes/skills/cassette-video-edit
curl -fsSL https://raw.githubusercontent.com/Cassette-Editor/oh-my-cassette/v0.6.0/skills/cassette-video-edit/SKILL.md \
-o ~/.hermes/skills/cassette-video-edit/SKILL.mdSet mcp_servers.cassette.timeout: 600 in ~/.hermes/config.yaml so a call's bounded waits fit. Hermes is a plain MCP host here: the 0.4 gateway/plugin layer is gone.
Any other MCP host
Command uvx, args ["oh-my-cassette==0.6.0"], stdio transport, a tool timeout of 10 minutes, and the environment variables above. No uv? pipx run oh-my-cassette==0.6.0 works too.
How a turn looks
you: Import intro.mp4 and beach.mov, then make a 20-second cut with the title "Kota Kinabalu".
agent: (calls the service's import tool with ["intro.mp4", "beach.mov"])
bridge: transcodes both videos, uploads them, passes the service media references
(calls the service's editing tool with your sentence, verbatim; progress streams while it runs)
"Done. Added a 20 s cut from both clips with the title at the start (v0→v3). Open it: <editor link>"
you: Export it.
agent: (calls the service's export tool)
bridge: downloads the MP4 to cassette-exports/kota-kinabalu.mp4The editor link comes from the service: open it in a browser to watch the run live and take over by hand.
Check a service
uvx oh-my-cassette==0.6.0 check --url http://127.0.0.1:8790/mcp --upload \
--arguments '{"project_id": "<a test project>"}'Prints one line per check (transport and token, contract declaration, instructions, tool schemas,
local-file and prepare declarations, and with --upload a real upload round trip; --arguments
are the file tool's other arguments for it) and exits 1 on any failure. --json gives
machine-readable output for the service's CI. The service must speak MCP 2026-07-28; one that only
answers the handshake-era initialize fails the transport check, and the bridge refuses it with
bridge.protocol_unsupported.
Update
Bump the pin in your host config (oh-my-cassette==<new version>) or reinstall the plugin; uvx fetches the new wheel on next start. Releases: CHANGELOG.md.
Development
git clone https://github.com/Cassette-Editor/oh-my-cassette && cd oh-my-cassette
uv sync --group dev
uv run pytest -q # reference service on loopback, no network; needs ffmpeg
uv run oh-my-cassette # the bridge itself (stdio)
uv run oh-my-cassette check --url <endpoint> # conformance checktests/reference_remote.py is an executable reference service for the contract;
RELEASING.md has the PyPI release flow.
Credits
The demo footage is by yasin ilcebay, licensed CC BY 3.0, from Wikimedia Commons: Aizanoi Temple of Zeus, Cappadocia, Datça, Ephesus and Gölcük Lake. The teaser trims and reorders it, and adds place names, a title and music.
The music is "Pamgaea" by Kevin MacLeod (incompetech.com) Licensed under Creative Commons: By Attribution 4.0 https://creativecommons.org/licenses/by/4.0/ The teaser uses an excerpt of it.
License
The code is MIT. Oh My Cassette is the client; the Cassette service has its own terms.
The demo recording (assets/demo-claude-code.gif), the teaser frames (assets/turkey-teaser.jpg)
and the teaser video attached to the release contain the works credited above. They are not covered
by the MIT license and remain under their own licenses (CC BY 3.0 for the footage, CC BY 4.0 for
the music).
MCP registry: mcp-name: io.github.Cassette-Editor/oh-my-cassette
Available Tools
1 toolcassette_bridge_statusCassette connection statusARead-only
Check whether the Cassette backend is reachable and compatible. Waits briefly for a reconnect. The editing tools are only listed while the backend is connected.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover the read-only safety profile, but the description adds genuinely non-obvious behavior: the call waits briefly for a reconnect before returning, and it gates visibility of other tools. That latency and dynamic-tool-listing context is exactly the beyond-annotation detail an agent needs. It stops short of describing the result shape.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three short sentences, front-loaded with the core purpose, then the blocking behavior, then the consequence for other tools. Every sentence adds information and nothing is padded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description carries the burden of explaining results, and it only implies a reachable/compatible verdict rather than stating what a failure looks like or whether it blocks. For a zero-parameter status probe this is close to sufficient, but not fully complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes no parameters, so there is nothing for the description to disambiguate; the baseline of 4 applies. No misleading parameter claims are made.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource — checking whether the Cassette backend is reachable and compatible — and adds the scope of what 'compatible' means operationally (editing tools only listed while connected). An agent can tell exactly what this tool answers without opening anything else.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It implies when this is useful by explaining that the editing tools only appear while the backend is connected, so an agent missing those tools can infer it should call this. However, there is no explicit 'call this when X' instruction or named alternative, so usage remains inferred rather than stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
17 tool updates
v0.5.0- Removed
cassette_answer_question - Added
cassette_bridge_status - Removed
cassette_cancel_job - Removed
cassette_config - Removed
cassette_edit - Removed
cassette_ingest_media - Removed
cassette_jamendo_setup - Removed
cassette_job_status - Removed
cassette_list_assets - Removed
cassette_login - Removed
cassette_make_prompt - Removed
cassette_match_bgm - Removed
cassette_match_exact_bgm - Removed
cassette_review_completion - Removed
cassette_run_job - Removed
cassette_timeline - Removed
jamendo_music_matcher
16 tool updates
v0.4.19- Changed
cassette_answer_question1 field changed- added
Output schema / $defs / Artifact / properties / expires_atAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Expires At" +}
- Changed
cassette_cancel_job1 field changed- added
Output schema / $defs / Artifact / properties / expires_atAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Expires At" +}
- Changed
cassette_config1 field changed- added
Output schema / $defs / Artifact / properties / expires_atAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Expires At" +}
- Changed
cassette_edit1 field changed- added
Output schema / $defs / Artifact / properties / expires_atAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Expires At" +}
- Changed
cassette_ingest_media1 field changed- added
Output schema / $defs / Artifact / properties / expires_atAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Expires At" +}
- Changed
cassette_jamendo_setup1 field changed- added
Output schema / $defs / Artifact / properties / expires_atAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Expires At" +}
- Changed
cassette_job_status1 field changed- added
Output schema / $defs / Artifact / properties / expires_atAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Expires At" +}
- Changed
cassette_list_assets1 field changed- added
Output schema / $defs / Artifact / properties / expires_atAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Expires At" +}
- Changed
cassette_login1 field changed- added
Output schema / $defs / Artifact / properties / expires_atAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Expires At" +}
- Changed
cassette_make_prompt1 field changed- added
Output schema / $defs / Artifact / properties / expires_atAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Expires At" +}
- Changed
cassette_match_bgm1 field changed- added
Output schema / $defs / Artifact / properties / expires_atAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Expires At" +}
- Changed
cassette_match_exact_bgm1 field changed- added
Output schema / $defs / Artifact / properties / expires_atAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Expires At" +}
- Changed
cassette_review_completion1 field changed- added
Output schema / $defs / Artifact / properties / expires_atAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Expires At" +}
- Changed
cassette_run_job1 field changed- added
Output schema / $defs / Artifact / properties / expires_atAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Expires At" +}
- Changed
cassette_timeline1 field changed- added
Output schema / $defs / Artifact / properties / expires_atAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Expires At" +}
- Changed
jamendo_music_matcher1 field changed- added
Output schema / $defs / Artifact / properties / expires_atAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Expires At" +}
1 tool update
v0.4.16- Added
cassette_jamendo_setup
1 tool update
v0.4.11- Added
cassette_login
2 tool updates
v0.4.10- Changed
cassette_config1 field changed- changed
Input schema / properties / thinking_level / anyOfPrevious value: -[ - { - "enum": [ - "low", - "medium", - "high" - ], - "type": "string" - }, - { - "type": "null" - } -]New value: +[ + { + "enum": [ + "off", + "minimal", + "low", + "medium", + "high", + "xhigh" + ], + "type": "string" + }, + { + "type": "null" + } +]
- Changed
cassette_run_job2 fields changed- changed
Input schema / properties / thinking_level / anyOfPrevious value: -[ - { - "type": "string" - }, - { - "type": "null" - } -]New value: +[ + { + "enum": [ + "off", + "minimal", + "low", + "medium", + "high", + "xhigh" + ], + "type": "string" + }, + { + "type": "null" + } +] - changed
Input schema / properties / wait / defaultPrevious value: -falseNew value: +true
12 tool updates
v0.4.6- Added
cassette_answer_question - Added
cassette_cancel_job - Added
cassette_config - Added
cassette_edit - Added
cassette_job_status - Added
cassette_make_prompt - Added
cassette_match_bgm - Added
cassette_match_exact_bgm - Added
cassette_review_completion - Added
cassette_run_job - Added
cassette_timeline - Added
jamendo_music_matcher
3 tool updates
v0.1.1- Removed
cassette_edit - Removed
cassette_make_prompt - Removed
cassette_timeline
5 tool updates
v0.1.0- First observed
cassette_edit - First observed
cassette_ingest_media - First observed
cassette_list_assets - First observed
cassette_make_prompt - First observed
cassette_timeline
TDQS
Scored across 1 tool
With only one exposed tool, there is no possibility of confusion about which tool to select. Its purpose (connectivity/compatibility check for the Cassette backend) is explicit and unambiguous.
The single name cassette_bridge_status is descriptive, snake_case, and readable, but follows a noun_phrase convention rather than a verb_noun action pattern. With only one name, consistency cannot really be violated, so this is a minor deduction at most.
A single gate/status tool is thin for a server whose apparent domain is cassette editing; the description admits the real editing tools only appear once the backend connects. The surface as listed is one trivial check.
The server's stated purpose (editing cassettes) has no create/read/update/delete operations exposed at all — only a reachability check. This is a severely incomplete surface that would block any actual work.
Maintenance
Related MCP Connectors
Video editor you drive by chat: send clips, say what you want, get the MP4 with cuts and captions.
Edit your own videos by chatting in Claude or ChatGPT: cut, caption, add music and export.
Agentic AI video editor: edit a real timeline, generate clips, music and voice, then render.
Edit video by talking to your AI — search footage, cut timelines, apply effects, add captions.
Related MCP Servers
- FlicenseNot gradedqualityAmaintenanceEnables users to turn folders of raw video clips into finished, beat-synced montage cuts through natural-language chat, with the agent handling shot selection, music matching, subtitles, transitions, picture-in-picture, preview digests, and rendering.-
- FlicenseNot gradedqualityAmaintenanceTurns a folder of raw clips plus a plain-language prompt into a finished, beat-synced montage, handling shot selection, music matching, subtitles, transitions, and picture-in-picture from within a chat agent. Returns a reviewable timeline digest and contact sheet before any frame is rendered.-
- FlicenseNot gradedqualityAmaintenanceEnables chat-driven video editing by letting an MCP host analyze raw clips and produce finished, beat-synced montages with matched music, subtitles, transitions, and picture-in-picture, all previewed as a timeline digest before rendering. It runs locally through agents such as Claude Code, Codex, Hermes, and OpenCode.-
- FlicenseNot gradedqualityAmaintenanceEnables users to turn folders of raw clips into finished, beat-synced montage videos through natural-language chat, handling shot selection, pacing, music matching, subtitles, transitions, and rendering. It returns a readable timeline digest and contact sheet each turn so the cut can be reviewed and approved before anything is rendered.-