An MCP server for OBS Studio that can hear. Subscribes to OBS events and audio meters, so an assistant can react to the show instead of only polling it.
Strong tool definitions with comprehensive descriptions and well-structured schemas. All 9 tools have detailed, LLM-optimized descriptions (avg 380 chars, well above 194-char baseline). Input schemas are complete with proper types and enums. However, output schemas are not documented, responses are inferred from context rather than formally specified. Error handling guidance is present in descriptions but not structured as recovery patterns. Tool naming follows verb_noun convention consistently (obs_audio_routing, obs_replay_buffer, etc.). No security issues detected (no secrets in params). Composition is sound, each tool has a single responsibility.
Read - and optionally change - WHERE one audio input goes: which recording/stream tracks carry it, whether OBS monitors it out of the speakers, its A/V sync offset, and its stereo balance. Call it with only inputName to read; every other field is a change. This is separate from mute and volume on purpose: those are 'how loud', these are 'does it reach anyone at all', and a source can be at 0 dB, unmuted, and still inaudible to viewers. TRAPS, all of which return success while doing nothing you wanted: (1) syncOffsetMs is capped near 960 ms - OBS accepts far larger values, stores them, and applies nothing, so 2000 ms is silently 0. The protocol declares a floor of -950 on the negative side but a ceiling of 20000 on the positive one, so only the positive side gets to fail quietly. If a feed is more than a second late, cut the real latency; you cannot compensate past the cap. (2) A source on no track at all does not even meter, so it looks like dead hardware. (3) Only the tracks named by RecTracks reach the recording, and only one reaches the stream; a source on any other track meters perfectly and reaches nobody. That is how a detection microphone exists without being heard, so do not 'fix' such a source onto track 1. (4) monitorType anything but NONE sends this to the room speakers, where the microphone picks it up and it goes out a second time, out of phase. MONITOR_AND_OUTPUT is the one that does both. The reply always contains what OBS holds AFTER the write, plus a `notes` list naming any of the above that is now true - trust that over the value you sent.
The whole audio routing picture in one call: every input that carries audio, with its tracks, monitoring, sync offset and balance, and an explicit list of what is misrouted. Read-only. Start here when audio is wrong and you do not yet know which source is at fault - the alternative is four requests per input, and the answer is usually a comparison between sources rather than a fact about one of them. It names the failures that a level meter cannot show you: sources on no track (inaudible AND unmeterable), sources off track 1 (they meter but never reach the stream or the recording on the reference machine), monitoring left enabled (doubled, out-of-phase audio via the room microphone), sync offsets stored past the ~960 ms cap where OBS applies nothing, and balances pushed off centre. Inputs with no audio at all are skipped rather than listed as errors.
Output schemas not documented. Tool descriptions explain what is returned (e.g., 'returns the path', 'returns the whole audio routing picture') but formal response schemas are absent. LLMs cannot plan downstream tool calls or extract fields reliably without knowing the structure.
Error handling lacks structured recovery guidance. Descriptions mention traps and failure modes (e.g., 'syncOffsetMs is capped near 960 ms', 'save is asynchronous') but do not provide actionable next steps for the LLM when errors occur. No error classification (retryable vs. user-fixable vs. fatal).
Inferred effective spec: <=2025-11-25.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 63 | <=2025-11-25 | v2 |
Read the filter chain on a source, or one filter in full detail. Do this before changing anything: every other filter tool addresses filters by exact name, and the settings you get back are the keys that actually exist for that kind. THE SETTINGS OBJECT IS PARTIAL. OBS only reports values that DIFFER from the kind's defaults, so a filter showing {} is running entirely on defaults, not broken and not empty. This is the reason a compressor here reports five keys while a delay reports one. It also means you cannot learn a kind's full parameter list from an existing filter - use obs_filter_kinds with filterKind for the declared defaults. Order is returned lowest index first, which is the order audio and video actually flow through. Omit filterName for the whole chain.
List every filter kind this OBS install can create - 43 on the reference machine - and optionally the default settings for one of them. This is the honest way to find out whether a PLUGIN actually loaded, because plugin filters only appear here once their DLL is in place: move_source_filter and its siblings come from the Move plugin, ndi_filter from the NDI runtime. If a filter kind you expect is missing, the plugin did not load and no amount of retrying CreateSourceFilter will help - it fails with an invalid-kind error that reads like a typo. Kind strings are exact and several ship as v2 alongside a legacy original (chroma_key_filter_v2, color_filter_v2, noise_suppress_filter_v2). Prefer the v2. Pass filterKind to also fetch that kind's defaults - though be aware OBS returns an empty object for kinds that register no explicit defaults, which means 'nothing declared', not 'no settings'.
Tune a filter that already exists - the threshold on a gate, delay_ms on a sync delay, opacity on a colour correction, similarity on a chroma key. By default your keys are MERGED into the existing settings, so you can send one value and leave the rest alone. Setting replace:true instead resets the filter to its kind defaults and then applies only what you sent, silently discarding every other tuned value on that filter. On a mic chain that has been dialled in over days, replace:true on a compressor is how you lose the ratio, the attack and the release while only meaning to change the threshold. Use the default merge unless you specifically want a clean slate. Setting keys are per kind and are not validated: an unknown or misspelled key is accepted, stored and ignored, and nothing reports an error. The response returns the filter's real settings afterwards - compare them against what you sent, because a key that silently vanished was the wrong name. Read obs_filter_get first to see the keys a kind really uses. Changes apply live to a running broadcast the instant they land; there is no staging.
Switch a filter on or off without removing it. Settings are kept, so this is the reversible way to find out what a filter is actually doing - toggle the noise suppression off, listen, toggle it back. Reach for this instead of obs_filter_remove whenever the question is 'is this helping', because removing loses the tuning and there is no undo. A disabled filter stays in the chain and keeps its index, so the numbering does not shift under you while you experiment. The effect is immediate on the live output. Disabling a sync delay on a camera will jump that camera out of lip-sync on stream at once, not at the next scene change.
The replay buffer: OBS holding the last N seconds of the show in memory so you can decide AFTER something happens that you wanted to keep it. This is the only capture primitive that works backwards in time, which makes it the one an automated producer actually needs - by the time a moment is recognisable as good, recording it is already too late. action 'save' is the interesting one: it writes the buffer to a file and returns the path. TRAPS: (1) The save is asynchronous. SaveReplayBuffer returns before the file is written, so asking for the filename immediately gives you the PREVIOUS clip. This tool polls until the path changes and reports `pathChanged: false` if it never did - do not hand a path onward without checking that flag. (2) The buffer only holds its configured window, 20 seconds on the reference machine, so 'save that thing from a minute ago' is not a thing that can succeed. (3) Every action except status fails with code 501 unless the buffer is already running, and it does not start itself - check status first. (4) The buffer is a separate encode from the stream; starting it costs CPU that a live broadcast is already using. (5) These requests drive OBS's MAIN replay buffer only. The reference machine also had a second replay_buffer output called 'Vertical Backtrack' which they do not touch at all - status lists it so you can see it is there.
Write a frame of a scene or source straight to a file on disk, and return the path. Use this instead of the image-returning screenshot tool whenever the picture is for a FILE - a thumbnail, a clip poster, a before/after pair, anything sampled on a loop. That tool base64s the whole image back through the conversation, which for a 1080p frame is megabytes of context spent on something nobody is going to look at. This one costs a path. Keep using the other one when you personally need to SEE the frame to judge framing or spot a black camera. TRAPS: (1) OBS writes the file itself, so imageFilePath is resolved on the machine running OBS, not wherever this MCP server lives, and it must be absolute. Point it at a directory that already exists. (2) imageWidth and imageHeight are 'scale to inner' - the aspect ratio is kept and the smaller ratio wins, so passing both does not crop, it fits. Pass one, or neither for native resolution. (3) imageFormat must be one this OBS build compiled in; png, jpg and webp are the safe ones, and GetVersion's supportedImageFormats is the real list. (4) A source that is not currently rendering gives you a black or stale frame, not an error.
OBS's virtual webcam output - the program feed presented to the operating system as a camera, so Zoom, Discord, a browser or a second OBS can consume it. Safe to touch during a live broadcast: it is a separate output and starting or stopping it changes nothing that viewers see, unlike the stream and record controls. TRAP: prefer 'start' and 'stop' over 'toggle'. Toggle is the single most common way an automated caller turns the virtual camera OFF while meaning to turn it on - it acts on a state it did not check, and it returns the state it produced rather than the one you wanted. 'status' is read-only; it reports only whether the output is active, not who is consuming it, so a running virtual camera with nobody watching looks identical to a working one.
Enum values in obs_virtual_cam and obs_replay_buffer are OBS API constants (e.g., 'OBS_MONITORING_TYPE_NONE') rather than human-friendly labels. LLMs may struggle to map user intent ('turn off monitoring') to the correct enum value without additional context.
obs_filter_settings accepts arbitrary keys in the 'settings' object with no validation. Description warns 'unknown or misspelled key is accepted, stored and ignored', this invites silent failures. LLMs cannot self-correct without explicit feedback on which keys are valid.
obs_save_screenshot requires absolute file paths on the OBS machine. Description warns of this but does not provide guidance on how an LLM should construct valid paths or handle path resolution errors. No fallback or default directory suggested.