obs-action-history
Provides tools for interacting with OBS Studio, enabling control of scenes, sources, filters, audio routing, media, and more, along with historical event insights such as audio levels and media status.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@obs-action-history Is my microphone picking up audio right now?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
obs-action-history
An MCP server for OBS Studio that can hear.
There are already several of these. Most of them wrap the obs-websocket request surface one call at a time and stop there. That is a reasonable thing to build, and it produces a tool that can operate OBS competently. It also produces a tool that is, in a specific and consequential way, deaf.
This one subscribes to the event stream, keeps a bounded record of what happened, and answers questions about it.
Zero dependencies. Node builtins only. Windows, macOS and Linux.
The distinction that matters
A request answers exactly one kind of question: what is true at this instant. You ask, OBS replies, you decide. Whatever occurred between one call and the next is gone, and you have no way of knowing it was ever there.
Now, that sounds like an architectural footnote. It is not. Consider what it costs you.
There is no request in the obs-websocket protocol that returns an audio
level. GetInputVolume gives you the fader position. GetInputMute gives
you a boolean. Neither has anything to say about whether sound is actually
coming out of that microphone. Levels exist in one place only, as
InputVolumeMeters, which is an event.
So the question "is my microphone working right now" cannot be answered by a server built purely on requests. Not answered poorly. Not answered slowly. Not answered at all. A server exposing 148 tools has precisely the same blind spot as one exposing twelve, because the answer is not on the surface either of them is drawn from.
This server holds that stream:
obs_who_is_talking -> Mic A peak -36.9 dB 29 samples
Mic B peak -37.8 dB 29 samples
Music peak -54.2 dB 29 samplesInputVolumeMeters arrives roughly fifty times a second per source. Nobody
wants three thousand raw frames back from a tool call. The question a person
actually has is who was loud, so meters never enter the buffer at all. They
are reduced to a peak per source and returned as an answer.
Related MCP server: obs-mcp-server
What this buys you, concretely
A microphone that is configured correctly and produces nothing. Fader at unity, not muted, and the wrong device selected or a cable quietly dead. Every setting a request can reach reports perfect health. This is not hypothetical; it is why the rig this came from needed a separate microphone-checking process before this existed.
A camera that follows the voice. You need to know which of two microphones is louder, continuously, and you need to compare them against each other rather than against some fixed threshold, because two microphones in one room have different gains and each of them hears everybody. There is nothing here to poll. The information only arrives as it happens.
Something alive and wedged, which is worse than something dead. During
development this server surveyed five media sources and every one of them
reported PLAYING. One had advanced zero milliseconds while the others moved
about 2,540. By state they were indistinguishable. Only elapsed time separated
a working feed from a corpse, and that exact blindness had already concealed
seventeen hours of silent music behind a dashboard showing green.
What happened two minutes ago. A poller can describe the present and nothing else. Once something has passed, it is simply unavailable, and you are reduced to guessing about the very incident you are trying to explain.
The descriptions are part of the product
A tool description is not a place to restate the parameter list. The model can already read the schema. It is where you put the things that will otherwise be learned the expensive way:
OBS audio sync offset caps near 960 ms. Larger values apply silently as nothing, so you believe you compensated for a two second delay and you compensated for none of it.
Scene item index 0 is the bottom, and a full-canvas source sitting above a background conceals it utterly, with no error raised anywhere.
A source parked off-canvas is still visible and still plays its audio. Hiding it instead does cut the audio, which is why an audio-only overlay is parked rather than hidden.
RemoveInputreports success and does not delete a source that anything still references.Bounds fields are inert unless
boundsTypeis set first.TriggerHotkeyByNametakes a bare name, andlibobs.muteis registered once per audio source, twenty-four times on the reference machine. Hotkeys therefore cannot address a specific source, whatever you might reasonably assume.
Each of those cost somebody something. They are written down because a model that does not know them will act confidently and be wrong, which is a good deal worse than acting tentatively and being right.
The tool that makes a fresh machine possible
obs_input_property_items enumerates the real choices behind a source
property: every webcam and every audio device, with the identifiers OBS
actually expects.
Microphone (Some USB Mic)
-> {0.0.1.00000000}.{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}There is nothing human-readable in that to guess at. Without this call, an assistant can only adjust sources a person already created by hand. With it, it can build them from nothing.
What is covered
67 tools across eleven modules.
Module | What it reaches |
scene items | add, remove, duplicate, z-order, lock, blend, the full fifteen-field transform |
filters | complete CRUD, reordering, renaming, and every filter kind the install can create |
audio routing | monitor type, sync offset, track assignment, balance, special inputs |
capture | replay buffer including save, virtual camera, screenshot to disk, record chapters, file splitting |
studio mode | preview scene, and the transition that puts it to air |
inputs | device enumeration, properties buttons, remove, rename, kind reference |
outputs | enumerate, status, settings, start and stop |
media | transport control, and a status probe that reports cursor movement |
hotkeys | list and trigger, the only route to plugin features that have no request of their own |
projectors | monitors, and fullscreen output of a mix or a single source |
core | scenes, sources, streaming, recording, screenshots, and a raw escape hatch |
Install
You need Node 22 or newer, for the global WebSocket, and OBS 31+ with Tools → WebSocket Server Settings → Enable WebSocket server ticked.
Copy .mcp.example.json into your MCP client's configuration and point args
at server.js. The password is read from OBS_WEBSOCKET_PASSWORD when it is
set, and otherwise from a secrets.json beside the server:
{ "obsPassword": "the value from OBS > Tools > WebSocket Server Settings" }Be aware that a wrong password does not present as a wrong password. OBS accepts the socket and then closes it with code 4009, which most clients report as a timeout, and you will spend your afternoon investigating your network. This server names it correctly.
The reference machine
Figures quoted throughout, such as the 43 filter kinds, the 411 hotkeys of which only 88 are distinct, the 960 ms ceiling, the five outputs and two replay buffers, were measured on OBS 32.2.1 with obs-websocket 5.7.4 on Windows, while that machine was broadcasting live to three platforms. That is what the reference machine refers to wherever it appears. Your install will differ in places, and every one of those numbers is checkable with the tools here, which is the point of stating them rather than rounding them off into vagueness.
Two errors in the published obs-websocket documentation surfaced this way and
are worked around. GetSourceFilterKindList returns sourceFilterKinds,
where the documentation says filterKinds. And SetSourceFilterSettings.overlay
defaults to true, where a summary claimed false; passing false calls
obs_source_reset_settings and destroys every other tuned value on that
filter, which is the sort of mistake you make once.
Contributing
mcp/tools/index.js holds the contract. A module exports
(obs) => [ { name, description, inputSchema, handler } ] and may use
obs.request(type, data) and nothing else.
Loading is deliberately fail-safe. A module that is missing, that throws while building, that returns a malformed tool or that duplicates a name is logged and skipped, and the server still starts with everything else intact. Your broken module is your problem and should not become somebody else's dead broadcast.
Before you open a pull request:
npm run preflightIt refuses credentials, absolute paths, machine-specific addresses and device identifiers anywhere in the tree, and it verifies that every module still loads.
Status
0.1.0. Tool names may still move before 1.0. Pin an exact version if you
are scripting against them.
Licence
MIT. See LICENSE.
Available Tools
67 toolsobs_audio_controlA
Get or set mute state and volume for an audio input. Volume is in dB (0 = unity, -100 = silence).
Inputs: inputName (required), muted, volumeDb.
obs_audio_routingA
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.
Inputs: inputName (required), tracks, balance, monitorType, +1 more.
obs_audio_routing_mapA
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.
Inputs: onlyProblems.
obs_create_inputA
Create a new source in a scene. Common kinds: 'dshow_input' (USB webcam/capture card), 'ffmpeg_source' (SRT/RTMP/RTSP network feed or video file), 'browser_source', 'wasapi_input_capture' (microphone), 'wasapi_output_capture' (desktop audio), 'text_gdiplus_v3', 'color_source_v3', 'image_source', 'ndi_source' (needs the NDI plugin).
Inputs: inputKind (required), inputName (required), sceneName (required), inputSettings.
obs_create_sceneA
Create a new empty scene. It is created but NOT switched to, so this is safe while live. A new scene is empty in the literal sense: no background, no audio, nothing inherited from any other scene, so cutting to it before adding sources shows black. Build it first with obs_scene_item_add, then switch. Scene names are unique across the collection and are what every other tool addresses, so pick one you can live with; renaming later is safe inside OBS and breaks anything outside it that refers to the old name.
Inputs: sceneName (required).
obs_filter_getA
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.
Inputs: sourceName (required), filterName.
obs_filter_kindsA
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'.
Inputs: filterKind.
obs_filter_orderA
Move a filter within the chain. Index 0 runs FIRST, and each filter processes what the one before it produced, so the order is a signal path and not a preference.
This is why the mic chain here reads noise suppression, then compressor, then limiter: the compressor is levelling speech rather than chasing room hiss up and down, and the limiter has the last word on the peaks. Put the limiter first and it clamps a signal the compressor has not levelled yet, then the compressor raises everything back up past the ceiling the limiter was there to hold. Video is the same idea - crop before scale, and a colour key before a colour correction that would move the colour you are keying on.
Indexes are contiguous and renumber after every move, so read the chain again rather than reusing numbers from before. Valid range is 0 to 8192; the resulting chain comes back.
Inputs: index (required), filterName (required), sourceName (required).
obs_filter_removeA
Delete a filter from a source, permanently. Its settings go with it and the websocket has no undo - if you might want it back, obs_filter_toggle bypasses it instead and keeps the tuning.
Removing renumbers everything below it in the chain, so any filterIndex you noted before this call is stale afterwards. Re-read with obs_filter_get before reordering.
Filters are per source, so this affects every scene that uses the source, not just the scene you happen to be looking at. The response returns the chain that is left.
Inputs: filterName (required), sourceName (required).
obs_filter_renameA
Rename a filter. The name is the only handle every other filter request has - there is no filter id - so renaming is the one edit here that can break something outside OBS: scripts, panel code and Move-plugin filters all address filters by exact name and will simply fail to find the old one afterwards. On the reference machine the sync delays are named consistently and the music ducking compressors DUCK, DUCK-IRL and so on, and tooling looks for exactly those strings.
Names must be unique per source. Renaming does not change the filter's index, its settings or whether it is enabled.
Inputs: newName (required), filterName (required), sourceName (required).
obs_filtersA
List filters on a source, or add one. Useful filters: 'noise_suppress_filter_v2' (RNNoise mic cleanup), 'noise_gate_filter', 'compressor_filter', 'chroma_key_filter_v2', 'color_filter_v2', 'async_delay_filter' (sync a lagging network camera to the others).
Inputs: sourceName (required), addFilterKind, addFilterName, addFilterSettings.
obs_filter_settingsA
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.
Inputs: settings (required), filterName (required), sourceName (required), replace.
obs_filter_toggleA
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.
Inputs: filterName (required), sourceName (required), enabled.
obs_get_input_settingsA
Read the full settings object of one input (resolution, device id, URL, file path...).
Inputs: inputName (required).
obs_get_output_settingsA
Read one output's settings object. Use it before obs_set_output_settings, because the keys an output accepts depend entirely on its kind and there is no schema request for them - the object you get back IS the list of keys that mean anything.
Expect less than you think. adv_stream on the reference machine returns only bind_ip, ip_family, dyn_bitrate, low_latency_mode_enabled and new_socket_loop_enabled: network tuning, and nothing else. There is no server URL and no stream key in there. The destination lives on the stream SERVICE, which is part of the profile, not the output - so reading this to find out where the rig is streaming will not tell you, and neither will writing to it change where the rig streams. Bitrate and encoder settings are likewise profile-level, not here.
Inputs: outputName (required).
obs_healthA
Full health snapshot: OBS version, active scene, stream/record state, and live stats (CPU %, memory, FPS, render lag, encoding lag, skipped and dropped frames). Use this first to diagnose anything, and to check whether the machine is keeping up.
Inputs: no named parameters.
obs_hotkey_keyA
Press a key combination at OBS, as if typed while OBS had focus. Use this only when an action has no name in obs_hotkey_list and no request - for instance a plugin binding a user configured by hand in Settings > Hotkeys.
It is a blunt instrument and fails silently in both directions. If nothing is bound to the combination, OBS accepts the request and does nothing at all. If something you did not expect is bound to it, that fires instead - and OBS's default bindings include stopping the stream and the recording. There is no request that reports what is bound to what, so you cannot check first.
keyId values are the OBS_KEY_* identifiers from libobs/obs-hotkeys.h - OBS_KEY_F1, OBS_KEY_A, OBS_KEY_SPACE, OBS_KEY_NUM1 and so on - not browser key names and not raw characters. Modifiers go in keyModifiers, not in keyId.
Inputs: keyId, keyModifiers.
obs_hotkey_listA
List the hotkey action names OBS knows about, which is how you discover what PLUGINS can do - a plugin's actions usually have no obs-websocket request at all, so this list is their only index. Results are grouped into core OBS actions and plugin-provided ones, and deduplicated with a count, because the raw response on the reference machine is 411 strings of which 37 are repeats.
Pass filter to narrow by substring (case-insensitive), or pluginsOnly:true to skip the ~390 core entries and see just what the installed plugins registered.
THE TRAP THIS LIST REVEALS: a repeated name means the action exists once per source or per scene - libobs.mute is registered separately for every audio input - and the trigger request takes only a name, so you cannot address a specific one. When you see count > 1 here, do not trigger it; find the real request instead (SetInputMute, SetSceneItemEnabled, and so on). Names with count 1 that are plugin-provided are the ones worth triggering.
Inputs: filter, pluginsOnly.
obs_hotkey_triggerA
Fire an OBS hotkey action by name - the way to invoke plugin features that have no obs-websocket request of their own. On the reference machine that means the vertical-canvas plugin's separate recording and streaming pipeline (VerticalCanvasDock*) and Advanced Scene Switcher's start/stop and macro segments (*SwitcherHotkey). Get names from obs_hotkey_list.
DANGEROUS NAMES ARE IN THE SAME NAMESPACE. OBSBasic.StopStreaming, OBSBasic.ForceStopStreaming and VerticalCanvasDockStopStreaming all sit in that list and all end a live broadcast instantly; OBSBasic.StartStreaming starts one. There is no undo and no confirmation. This tool refuses nothing, so read the name you are about to send.
DO NOT USE IT AS A SHORTCUT FOR A REAL REQUEST. Duplicated names - libobs.mute, MediaSource.Play, libobs.show_scene_item.N - are registered once per source or scene, and this request takes a bare name, so which one fires is not something you control. Mute with SetInputMute, hide with SetSceneItemEnabled, switch scenes with SetCurrentProgramScene. Hotkeys are for what those cannot reach.
OBS returns success for a name it accepted, not for an effect it produced; verify the thing you wanted actually changed.
Inputs: hotkeyName (required), contextName.
obs_input_kind_referenceA
What source types this OBS build supports, and which settings keys each type legally accepts.
Call it with no arguments to list the kinds - that list is also the honest answer to 'which plugins are loaded', since a kind like 'ndi_source' only appears if its plugin initialised. Call it with inputKind to get that kind's default settings object, which is the closest thing to a schema OBS offers: an input's kind decides which settings keys mean anything, and a key that belongs to a different kind is accepted and silently ignored rather than rejected. That silent acceptance is why guessing keys wastes so much time - obs_set_input_settings returns success either way.
Two limits worth knowing. The defaults object lists only keys the plugin registered a default for, so it is a floor and not the full set: dshow_input returns nine keys and video_device_id is not among them, because it has no sensible default. To see the rest, read a WORKING input of the same kind with obs_get_input_settings, and get legal values for the dropdown keys from obs_input_property_items. Second, kind ids carry version suffixes - this build reports color_source_v3, text_gdiplus_v3, slideshow_v2 - and the versioned id is what existing inputs report and what obs_create_input expects. unversioned:true strips the suffixes, which is useful for recognising a kind named in an older config or article, but do not create sources with the stripped names.
Inputs: inputKind, unversioned.
obs_input_property_itemsA
Enumerate the real choices behind a dropdown in a source's Properties dialog - every webcam, capture card, microphone, speaker and monitor this machine has, with the exact device ID string OBS wants written back. This is the difference between configuring OBS on a machine somebody already set up by hand and configuring one from nothing: without it, pointing a capture source at a camera means GUESSING a Windows device path, and pointing an audio source at a mic means guessing a GUID like '{0.0.1.00000000}.{xxxxxxxx-...}' that appears in no documentation and is different on every PC. Read the list here, then write the chosen itemValue with obs_set_input_settings under the SAME property name.
Write back itemValue, never itemName. itemName is the label a human reads ('Logitech StreamCam'); itemValue is the device path, and it is what OBS matches on. itemEnabled false means the device is known to OBS but not selectable right now - typically another application holds it open.
propertyName is the plugin's INTERNAL id, not the label in the dialog. Verified on the reference machine: dshow_input (webcam / capture card): video_device_id, audio_device_id, res_type, resolution, video_format, color_space wasapi_input_capture and wasapi_output_capture (mic / desktop audio): device_id monitor_capture (screen): monitor_id, method Other kinds and other plugins have their own ids; the two failure messages tell them apart. 'Unable to find a property by that name' means the id is wrong. 'The property found is not a list' means the id is RIGHT but that property is a button, a checkbox, a path or a text box - which is itself useful, because it is how you confirm a button exists before pressing it with obs_press_input_button.
These values are not portable and not permanent: a USB camera moved to a different port gets a different video_device_id, which is the usual reason a scene that worked yesterday shows a black rectangle today. Re-read this list rather than reusing a stored string.
Inputs: inputName (required), propertyName (required).
obs_list_input_kindsA
List every source type this OBS install supports (tells you which plugins loaded).
Inputs: no named parameters.
obs_list_inputsA
List every input/source in OBS with its kind (video capture device, media source, browser, audio input, etc). Use this to see which cameras and mics OBS can actually see.
Inputs: no named parameters.
obs_list_outputsA
List every output OBS currently has - the stream, the recording, the replay buffer, the virtual camera, and any output contributed by a plugin - with which ones are running right now.
Start here before touching anything in this module, because the names are not predictable. OBS's own are internal ids (adv_stream, adv_file_output); plugin outputs use whatever label the plugin chose, and on the reference machine the vertical-canvas plugin contributes one called 'Vertical Backtrack' that also reports its width and height as 0 in this list, which is the plugin not filling those in rather than a broken output. Do not infer what an output does from its name.
The field that actually matters is outputFlags.OBS_OUTPUT_SERVICE. True means the output feeds a streaming service - that is the live broadcast, and on the reference machine exactly one output has it: adv_stream, which is the single RTMP connection to the local relay that fans out to Twitch, YouTube and Kick. Everything downstream of it dies together, so 'which platform does this stop' has no useful answer: it stops all three. This tool annotates each output with that reading so the distinction is not left to name-guessing.
Inputs: no named parameters.
obs_list_scenesA
List every scene, and which one is currently live (program) and previewed.
Inputs: no named parameters.
obs_list_sources_in_sceneA
List the sources (scene items) inside a scene, with their item IDs, visibility and transform. Needed before moving, resizing or hiding anything.
Inputs: sceneName (required).
obs_media_controlA
Transport control for a media source: play, pause, stop, restart, next, previous.
next and previous are PLAYLIST actions. They only do something on a vlc_source, which holds a list of files. On an ffmpeg_source - which is every media input on the reference machine, a looping media source included - there is no playlist, so they succeed and change nothing. A tool call that returns success while nothing happened is worse than an error, so check the returned state.
Careful with a looping media source specifically: its watchdog in music/player.js advances the queue on states ended, none and error, but NOT on paused or stopped. So pausing or stopping a looping media source here is not self-healing - the music stays silent until a person notices, which is the exact failure mode the reference machine already paid seventeen hours for. Use node music/player.js --next to skip a track instead.
Inputs: action (required), inputName, inputUuid.
obs_media_seekA
Move the playhead of a media source, either to an absolute position (positionMs) or by a relative amount (offsetMs, negative to rewind). Exactly one of the two.
There is NO bounds checking, by design of the protocol - seeking past the end does not clamp, and seeking a live feed that has no meaningful duration is not a defined operation. Read obs_media_status first: if durationMs is null and unboundedDuration is true, this source is a live stream and seeking it is meaningless.
On the reference machine, do not seek a looping media source to reach the next track. The queue lives in music/player.js, not in OBS; running node music/player.js --next is the supported skip and keeps the player's state file in agreement with what is audible.
Inputs: offsetMs, inputName, inputUuid, positionMs.
obs_media_statusA
Survey what media sources are actually playing. With no inputName it checks EVERY media input (ffmpeg_source and vlc_source) in one go, which is the form you almost always want: the failure worth catching is 'one of them quietly ended', and you cannot spot that by asking about a source you already suspect.
THE TRAP: mediaState PLAYING does not mean video or audio is arriving. A disconnected SRT feed sits at PLAYING with its cursor frozen at 0 forever - measured on the reference machine, every media input claimed PLAYING and only the cursor separated the working ones from a dead feed. Pass probeMs (e.g. 2000) to take a second sample and get advancing:true/false per source, computed from whether cursorMs actually moved. That is the only honest liveness test.
Why it matters here: background music is an ffmpeg_source named a looping media source whose next track is queued by a Node process listening for MediaInputPlaybackEnded. When that listener dies the source parks in state 'ended' and every other indicator - process table, supervisor, dashboard - stays green. That gap once hid seventeen hours of silence. state:'ended' on a looping media source is the whole diagnosis.
Note durationMs is null for live feeds: OBS returns INT64_MIN, not null, for a stream with no end, so unboundedDuration:true flags a live source rather than a broken one.
Inputs: probeMs, inputName, inputUuid.
obs_monitor_listA
List the physical displays OBS can put a fullscreen projector on, with the index each one answers to. Always call this before opening a fullscreen projector: the index is positional, it is not stable across replugging a display, and there is no request that tells you which one OBS's own window is on.
If this returns a single monitor - which is what the reference machine reports, one 1920x1080 at index 0 - then there is no spare screen, and every fullscreen projector will land on top of the operator's OBS window. Since obs-websocket has no way to close a projector, that has to be undone by hand at the machine. Use windowed mode instead when the count is 1.
Inputs: no named parameters.
obs_output_controlA
Start, stop or toggle any output by name - covers StartOutput, StopOutput and ToggleOutput. This is the general lever for outputs that have no dedicated tool: the replay buffer, the virtual camera, and above all the outputs a plugin brought with it, such as the vertical canvas's own recording on the reference machine. Without it, a whole second canvas is unreachable.
Know which output you are aiming at before you fire. Stopping the SERVICE output ends the broadcast for every viewer on every platform simultaneously - here that is adv_stream, the one RTMP connection into the local relay that feeds Twitch, YouTube and Kick, so there is no such thing as stopping just one platform from OBS. Stopping the local recording (adv_file_output) is merely annoying by comparison. Stopping a replay buffer discards what is in memory. Because these are so far apart in consequence and the names give no hint, this tool refuses to stop a service output unless you pass confirm:true, and names the output in the refusal.
For the main stream and the main recording, prefer obs_stream_control and obs_record_control. Those are the paths the rest of the reference machine's tooling and OBS's own state follow; this tool operates on the raw output and is the right choice only for what they do not cover.
'toggle' is convenient and dangerous in the same way: it decides based on state you have not looked at, so a toggle aimed at something you believed was stopped will stop it instead. Check obs_output_status first, or use explicit start/stop. Starting an already-running output, or stopping an already-stopped one, is an error rather than a no-op - this tool reports the state it found instead of failing obscurely.
Inputs: action (required), outputName (required), confirm.
obs_output_statusA
Live health of an output: whether it is running, how long it has been running, how many bytes it has pushed, how many frames it dropped, and how congested it is. Omit outputName to get all of them at once, which is the fastest read of 'what is this machine actually doing right now'.
Read outputActive first and let it gate everything else, because the counters are NOT cleared when an output stops. On the live rig, adv_file_output reported outputActive false alongside 1.68 GB of outputBytes and 131,933 outputTotalFrames - leftovers from the previous recording session. Reading bytes alone would say the recording is running when it has not been for hours. outputTimecode and outputDuration are the ones that do reset to zero, so they agree with outputActive.
For a stream that is up, the two numbers to watch over time are outputSkippedFrames and outputCongestion. Congestion is a percentage of how close the encoder is to not keeping up; rising congestion with climbing skipped frames means the upload or the encoder is the bottleneck and viewers are seeing it. outputReconnecting true means the connection dropped and OBS is trying to re-establish it - the broadcast is interrupted even though outputActive has not gone false.
One caution specific to this setup: healthy numbers here only prove OBS is successfully pushing bytes into the local relay. They say nothing about whether the relay is still forwarding to each platform. A YouTube broadcast can be receiving nothing while this reports a perfect stream.
Inputs: outputName.
obs_press_input_buttonA
Press a button inside a source's Properties dialog. OBS exposes a handful of actions only as buttons - there is no setting to write instead - so without this an agent simply cannot perform them, and two of them are the standard recoveries for the reference machine:
'refreshnocache' on a browser_source is the ONLY way to make OBS pick up an edited local file. The overlays here are local HTML in overlay, and OBS caches them: after editing one, writing the same url back with obs_set_input_settings is a no-op and the stream keeps showing the old page. Verified present on the reference machine's browser sources; note the id is 'refreshnocache', not 'refresh', which does not exist.
'activate' on a dshow_input (webcam / capture card) is how you recover a camera that dropped - the device stopped delivering frames and the source is showing black while OBS still believes it is fine. The id is 'activate' in BOTH directions: it toggles, and the dialog only changes the button's label to 'Deactivate'. There is no 'deactivate' property - asking for one returns 'Unable to find a property by that name'.
Two cautions. A press on a live source is visible on stream: toggling 'activate' on a camera that is currently on air drops it to black for as long as the device takes to re-open, and if the device is genuinely gone it stays black and you have just deactivated a working scene - check which scene is live first. And OBS returns nothing at all on success, so a clean result means 'the button was pressed', not 'the thing you wanted happened'; confirm with obs_screenshot.
Buttons are not discoverable through obs_input_property_items, which only enumerates dropdowns. What that tool does give you is existence: 'The property found is not a list' confirms the id is real before you press it, while 'Unable to find a property by that name' means it is not.
Inputs: inputName (required), propertyName (required).
obs_preview_sceneA
Read or set the scene sitting in PREVIEW - staged, rendering, and not on air. Setting this is the safe half of a scene change: nothing viewers see moves, so it is the one scene operation that is genuinely harmless during a live broadcast. Stage here, screenshot the scene by name to confirm the cameras are alive and the layout is right, then cut with obs_studio_transition. TRAPS: (1) Requires studio mode. With it off, both reading and setting fail with a bare code 506 that says nothing useful - this tool translates it, but the fix is always obs_studio_mode first. (2) Staging does not guarantee a feed is connected. Network sources - SRT, RTMP, NDI - accept a new connection only while they are being rendered, and how long that handshake takes is not something the preview tells you. Stage early, then confirm with a screenshot of the preview scene rather than assuming a staged source is a live one. (3) A staged scene is not frozen. It keeps updating, so a preview screenshot taken thirty seconds ago is not evidence about now. (4) Preview and program swap places after a transition, so whatever you just cut away from is now sitting in preview.
Inputs: sceneName.
obs_profile_and_collectionB
List / switch OBS profiles (encoder + streaming settings) and scene collections (scene layouts).
Inputs: setProfile, setSceneCollection.
obs_projector_open_mixA
Open a projector showing a whole video mix - program (what is going out), preview (what studio mode has queued up), or multiview (the grid of all scenes).
program on a second monitor is the standard clean-feed trick: a capture card or a second PC pointed at that display gets the finished broadcast picture without a second encode and without OBS chrome. preview is the confidence monitor for whoever is operating. multiview is for a director watching everything at once.
Defaults to a WINDOW. Pass monitorIndex only when obs_monitor_list shows a display you can afford to lose, because fullscreen covers that entire screen and obs-websocket cannot close or enumerate projectors - this window stays until someone closes it in the OBS UI.
Inputs: videoMixType (required), monitorIndex, projectorGeometry.
obs_projector_open_sourceA
Open a projector showing ONE source on its own, rather than the whole mix. sourceName takes a scene name as readily as an input name - in OBS a scene is a source - so this projects either a single camera, screen capture or media input, or an entire scene composed as it would look on air.
What it is for: feeding one specific picture to hardware or to a person. A guest's camera on a monitor they can see, one screen share to a capture card, a scene routed to a second machine - all without re-encoding anything, because the projector is just the same frames drawn to a window.
A source projector shows that source ALONE. Overlays that live in a different scene are not in it: on the reference machine the HUD is its own scene added into the others, so projecting a camera source gives you the bare camera, and projecting a scene gives you that scene's own composite. If you want what the audience sees, that is the program mix, not a source - use obs_projector_open_mix.
Defaults to a WINDOW. obs-websocket cannot close or enumerate projectors - this window stays until someone closes it in the OBS UI.
Inputs: canvasUuid, sourceName, sourceUuid, monitorIndex, +1 more.
obs_rawA
ESCAPE HATCH. Call any obs-websocket v5 request directly by name with a raw payload. Covers everything the wrapped tools do not, including plugin-provided vendor requests. See the obs-websocket protocol reference for request names.
Inputs: requestType (required), requestData.
obs_record_chapterA
Drop a named chapter marker into the local recording at this instant, so the VOD can be navigated later without anyone scrubbing through six hours of it. This is the cheap version of clipping: it costs nothing, it cannot fail loudly mid-show, and it turns one long recording into something with an index. TRAPS: (1) Recording must be ACTIVE. Streaming is not recording, and a rig can be live for hours with no recording file open at all; in that state this fails with code 501. Check obs_record_control status first. (2) Chapters only exist in Hybrid MP4. Any other container and the marker goes nowhere; OBS 30.2 onward supports no other format for this. The reference machine records hybrid_mp4, so it works here. (3) The marker lands where the recording is NOW, which is the live edge - you cannot chapter something that already happened. Name it for what is starting, not for what just ended.
Inputs: chapterName.
obs_record_controlA
Start, stop, pause, resume or query local recording (your local backup / VOD master).
Inputs: action (required).
obs_record_splitA
Close the recording file being written right now and immediately start the next one, without stopping the recording. Use it to cut a long show into pieces you can upload or hand off while the show is still going, rather than waiting six hours for one enormous file. The stream is untouched - this is the local recording only, and viewers see nothing. TRAPS: (1) Recording must be active; otherwise code 501. Streaming being live is not enough - the local recording is a separate output. (2) There is no 'undo' and no way to rejoin the halves afterwards without re-encoding. (3) The new file is named by OBS's filename formatting, so you do not choose the name here - read it back from obs_record_control status if you need it. (4) A split is a real container boundary: anything that was mid-chapter or mid-scene at that instant is split across two files.
Inputs: no named parameters.
obs_remove_inputA
Delete a source outright, removing it from every scene it appears in at once.
The trap: a clean result does NOT prove the source is gone. OBS drops its own reference and answers success, but the source itself survives as long as anything else still holds a reference to it - and those references are not all visible from the scene list. A plugin that maintains a second canvas (the vertical-canvas plugin on the reference machine does exactly that) keeps its own reference, so the input can vanish from obs_list_inputs while the decoder is still running and still costing GPU. If you removed something to reclaim resources and the machine did not get faster, this is why. This tool re-reads the input list afterwards and tells you which of the two happened.
So if the goal is to stop something DECODING rather than to delete it, do not use this at all: take it out of every scene, or hide it. A source that is in no active scene does not decode and does not burn GPU, and it is reversible - which matters here, because removal is not undoable over the websocket. Re-creating the input afterwards will not bring back its filters, its audio routing, its sync offset, or its position and crop in any scene. Read obs_get_input_settings first if there is any chance you will need to rebuild it.
Inputs: inputName (required).
obs_rename_inputA
Rename a source. Inside OBS this is safe and complete: scene items reference the source itself, not its name, so every scene follows the rename and nothing breaks visually.
Outside OBS it is the opposite, and that is the real risk. Everything that automates OBS addresses sources BY NAME over the websocket - health checks asking whether a mic is producing sound, audio tooling, scene builders, and every tool call in this server. None of them are updated by a rename, none of them fail loudly, and the symptom arrives later as 'that check stopped reporting' rather than as an error. A source name that has drifted from what it now shows is often still worth keeping for exactly this reason. Prefer renaming the SCENE, which nothing addresses by name, or leaving it alone.
Names are unique across the whole collection, so a rename onto a name already in use fails.
Inputs: inputName (required), newInputName (required).
obs_replay_bufferA
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.
Inputs: action (required), waitMs.
obs_save_screenshotA
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.
Inputs: sourceName (required), imageFilePath (required), imageWidth, imageFormat, +2 more.
obs_scene_item_addA
Add an EXISTING source to a scene, as a new scene item. This is the missing half of obs_create_input: that one makes a brand new source, this one places a source you already have into another scene. Reusing a source is almost always what you want - one camera placed in six scenes is one device opened once, while six separate inputs on the same webcam will fight over the device and most will fail to start.
A scene can be added to another scene this way, which is how layer scenes work: on the reference machine the HUD scene holds every overlay and is added as a SINGLE item into each scene, so editing HUD once changes all of them. Do not rebuild overlays per scene.
The new item lands at the TOP of the z-order and covers whatever it overlaps. If you are adding a background it has to be moved down to index 0 afterwards with obs_scene_item_order. The response reports the index it actually received, so check it rather than assuming.
sceneItemEnabled defaults to true. Note that a source can be enabled but sitting off-canvas: see obs_scene_item_transform for why that is not the same as hidden.
Inputs: sceneName (required), sourceName (required), enabled.
obs_scene_item_blend_modeA
Read or set how a scene item composites with the layers under it.
OBS_BLEND_NORMAL is plain alpha and is the default. OBS_BLEND_ADDITIVE and OBS_BLEND_SCREEN both drop black toward transparent, which is the usual trick for glows, light leaks and particle or scanline overlays shot on a black field - they let an overlay sit on top without boxing off what is underneath. OBS_BLEND_MULTIPLY and OBS_BLEND_DARKEN do the opposite and drop white, useful for shadowing or tinting a region. SUBTRACT and LIGHTEN are situational.
Blend mode only matters against what is BELOW the item, so it interacts directly with z-order: the same overlay set to additive looks completely different depending on its index. An item at index 0 has nothing under it to blend with and will look unchanged no matter what mode you pick - if a blend mode appears to do nothing, check the index first.
Omit blendMode to read the current one.
Inputs: sceneName (required), sceneItemId (required), blendMode.
obs_scene_item_duplicateA
Copy a scene item, with its transform, crop and blend mode intact, either within the same scene or into another one. Omit toScene to duplicate in place.
The copy points at the SAME underlying source - this clones the placement, not the camera. That is what makes it the fast way to build a matching layout: place one screen capture exactly, duplicate it into the next scene, then move only what differs. It is also why duplicating an audio-carrying source needs care, because you now have two items playing the same audio in one scene and the result is doubled, not louder.
Duplicating into a scene that already has that source is allowed; OBS does not deduplicate.
Inputs: sceneName (required), sceneItemId (required), toScene.
obs_scene_item_lockA
Lock or unlock a scene item. A locked item cannot be dragged or resized in the OBS window by a human, and cannot be transformed over the websocket either - SetSceneItemTransform on a locked item fails rather than silently doing nothing.
Worth using on the pieces that must not drift: a HUD layer or a background that someone might grab by accident while adjusting a camera on top of it. Locking changes nothing about what viewers see - it affects editing only, not visibility and not audio. If a transform keeps being rejected, read the lock state before assuming the coordinates were wrong.
Omit locked to read the current state without changing it.
Inputs: sceneName (required), sceneItemId (required), locked.
obs_scene_item_orderA
Read or change z-order - which source is drawn on top of which.
INDEX 0 IS THE BOTTOM. Higher index draws later, so it covers everything below it. This is upside down from the OBS window, which lists the topmost source first, and getting it backwards is the single easiest way to make a layer vanish.
On the reference machine the BG layer is deliberately parked at index 0 of every scene so no scene is ever pure black. Anything full-canvas and opaque placed above it hides it completely and the symptom is simply a black background with no error anywhere - two opaque black rectangles had to be hidden before BG showed at all. If a background stopped showing, check what is above it before you touch the background itself.
Call with sceneName only to read the whole stack bottom-first. Add sceneItemId and index to move one item; the full resulting order comes back either way. Indexes are renumbered contiguously after a move, so read the stack again before a second move rather than reusing the numbers you just saw.
Inputs: sceneName (required), index, sceneItemId.
obs_scene_item_removeA
Remove one scene item from its scene.
This removes the PLACEMENT, not the source. The same input placed in other scenes keeps working there, keeps its settings and keeps its filters - so deleting a camera from the BRB scene does not disturb it anywhere else. Only when you remove the last placement does the input itself lose its last reference, and its settings go with it, so the final copy is the one to think twice about.
There is no undo over the websocket. If the goal is just to get something off screen, obs_set_source_visible is reversible and this is not.
Inputs: sceneName (required), sceneItemId (required).
obs_scene_item_transformA
The complete transform: position, scale, rotation, alignment, crop and bounds. obs_set_source_transform covers position, scale and crop only; this one reaches the rest. Call with just sceneName and sceneItemId to READ the current transform, including the read-only width/height/sourceWidth/sourceHeight that tell you how big the thing actually is on canvas right now.
Only the fields you pass change; everything else keeps its current value.
BOUNDS ARE INERT WITHOUT boundsType. boundsWidth, boundsHeight and boundsAlignment are ignored while boundsType is OBS_BOUNDS_NONE, which is the default, so setting a bounding box and nothing else accomplishes nothing at all and reports success. Set boundsType in the same call. Bounds are the right tool for a slot of fixed size - OBS_BOUNDS_SCALE_INNER fits the source inside the box keeping its aspect ratio, which is how you drop a camera of unknown resolution into a layout without doing the arithmetic. Once bounds are active they drive the size and scaleX/scaleY stop being the thing to adjust.
ALIGNMENT IS A BITMASK, NOT AN ENUM: 0 centre, 1 left, 2 right, 4 top, 8 bottom, added together. 5 is top-left and is what every item on the reference machine uses. It sets which point of the source positionX/positionY actually refers to, so changing alignment alone appears to teleport the source even though position never changed.
Crop is measured in SOURCE pixels, before scaling, and cuts in from each edge. width/height/sourceWidth/sourceHeight cannot be written - to resize, set scale, or use bounds.
A source moved off the canvas is still ENABLED and still renders and, more to the point, its audio still plays. Parking something at x=2600 on a 1920 canvas does not silence it. That is what doubled both voices on day one, from a phone parked out of frame but unmuted; it is also used deliberately here, because the mic and music sources are off-canvas precisely so they stay audible without taking up pixels. Hiding a source with obs_set_source_visible DOES cut its audio, which is why those are parked rather than hidden. Choose the one you mean.
Inputs: sceneName (required), sceneItemId (required), scaleX, scaleY, +13 more.
obs_screenshotA
Capture what a scene or source looks like RIGHT NOW and return it as an image. This is how Claude visually verifies framing, layout and that a camera is actually alive instead of showing a black frame.
Inputs: sourceName (required), width.
obs_set_input_settingsA
Change settings on an input: swap a webcam's device, change an SRT/RTMP media source URL, change resolution or FPS. Pass only the keys you want to change.
Inputs: settings (required), inputName (required).
obs_set_output_settingsA
Write an output's settings. This REPLACES the settings object rather than merging into it, so read obs_get_output_settings first and send the full object with your change applied - sending one key on its own is how you lose the others.
Do not reach for this to change where the stream goes or how good it looks. The destination and stream key belong to the profile's stream service, and bitrate and encoder belong to the profile; none of them appear in an output's settings. What is here is network-level behaviour - which interface to bind, IPv4 vs IPv6, dynamic bitrate, low-latency mode. For anything about quality or destination, use obs_profile_and_collection.
Change settings while the output is stopped. A running output has already read its configuration, and a write that appears to succeed may do nothing until the next start - which reads as a broken tool when it is really a timing mistake. On a live broadcast, changing adv_stream's networking settings is not a safe experiment: verify with obs_output_status afterwards and be ready for the possibility that it takes the connection down.
Inputs: outputName (required), outputSettings (required).
obs_set_source_transformB
Position and size a source inside a scene, to build picture-in-picture and side-by-side layouts. Coordinates are in canvas pixels.
Inputs: sceneName (required), sceneItemId (required), scaleX, scaleY, +6 more.
obs_set_source_visibleA
Show or hide one source inside a scene (e.g. drop the overlay, hide a camera).
Inputs: visible (required), sceneName (required), sceneItemId (required).
obs_special_inputsA
Names of OBS's six built-in audio slots: Desktop Audio, Desktop Audio 2, and Mic/Aux 1-4. Read-only. The trap is what an empty answer means. Any slot a user never configured comes back null, and on a rig that creates its audio sources explicitly - which is every rig built by a script - all six are null. On this machine they are: every audio source here, microphones included, is a named input, not a special slot. Six nulls therefore means 'this OBS has no special inputs assigned, go read the input list', NOT 'this OBS has no audio' and NOT 'the request failed'. Use this to resolve the two names OBS itself uses in its settings UI; use obs_audio_routing_map to actually find the microphones.
Inputs: no named parameters.
obs_stream_controlA
Start, stop or query the live stream. Use 'status' freely. Be deliberate with start and stop, because stopping ends the broadcast for every viewer on every platform.
Inputs: action (required).
obs_studio_modeA
Read or set whether OBS is in studio mode - the two-stage layout where a scene can be staged in PREVIEW and inspected before it is cut to PROGRAM. This is the safety rail for anything automated. Switching scenes on a live broadcast is otherwise unreviewable: the request lands and every viewer sees the result before you do. With studio mode on, the sequence becomes stage -> screenshot the preview -> cut, and a camera that has not reconnected or a screen share showing the wrong window gets caught by you rather than by chat. If you are going to move scenes automatically, turn this on first. Turning it on or off changes NOTHING that viewers see - program keeps playing throughout, so this is safe to enable mid-broadcast. What it does change is the operator's OBS window, which matters if a human is also driving. TRAPS: (1) It is not a lock. With studio mode on, obs_switch_scene / SetCurrentProgramScene still cuts straight to air with no review. Studio mode ADDS the reviewed path; it does not close the unreviewed one. (2) Disabling it throws away whatever was staged in preview, silently. (3) Every preview request fails with code 506 while this is off, which is the single most common reason those tools appear broken.
Inputs: enabled.
obs_studio_transitionA
CUT TO AIR. Runs the current transition, taking whatever is in preview to program. This is the irreversible half of a studio-mode scene change and the moment every viewer on every platform sees the new scene - treat it exactly as seriously as switching the program scene directly, because that is what it does. Read obs_preview_scene and look at a screenshot of it before calling this. TRAPS: (1) Requires studio mode; code 506 otherwise. (2) It returns immediately, BEFORE the transition finishes. With a fade or a stinger configured, program is mid-transition when this resolves, so a screenshot taken right after shows a blend of two scenes rather than the new one. Wait out the transition duration before verifying. (3) It uses whatever transition and duration OBS currently has selected - this request takes no parameters and cannot override them. Check the current transition first if the timing matters. (4) Preview and program swap, so calling it twice in a row returns you to the scene you started from, which looks like it did nothing.
Inputs: no named parameters.
obs_switch_sceneA
Switch the LIVE scene. This is the POV cut. It never interrupts the stream; the encoder keeps running and viewers just see the camera change.
Inputs: sceneName (required).
obs_transitionB
List transitions, or set the active one and its duration (how POV cuts look).
Inputs: durationMs, transitionName.
obs_video_settingsA
Get or set canvas resolution, output resolution and FPS. Changing these requires the stream to be stopped.
Inputs: baseWidth, baseHeight, outputWidth, fpsNumerator, +2 more.
obs_virtual_camA
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.
Inputs: action (required).
obs_watchA
What OBS has been doing: recent events from the live event stream (scene changes, mute and visibility changes, media starting and ending, stream and record state, filter changes). Use it to see what happened rather than polling for what is true now. Optionally waits first, so you can watch for something you are about to trigger. Audio meters are NOT here - they are summarised by obs_who_is_talking instead, because they arrive ~50 times a second.
Inputs: forMs, limit, types.
obs_who_is_talkingA
Listen to the microphones for a moment and report who is actually speaking, loudest first. This is the reactive primitive: it answers 'which person should I cut to right now'. Returns a peak level in dB per audio source over the sampling window, so compare the sources against EACH OTHER rather than against a fixed threshold - the two mics have different gains and each usually picks up everyone in the room, so the loudest is the speaker, not whoever crosses a fixed number. A source that reports no level at all is muted, on no audio track, or its scene is not live.
Inputs: forMs, sources.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 67 tools
Each tool targets a distinct OBS resource and action (transform vs. visible vs. blend_mode; filter settings vs. toggle vs. order), and descriptions actively cross-reference related tools to prevent confusion. Minor overlap exists between obs_screenshot/obs_save_screenshot and the raw/hotkey escape hatches, but they are explicitly disambiguated.
All tools use a consistent obs_ prefix and snake_case with strong verb_noun patterns (obs_scene_item_*, obs_filter_*, obs_media_*), making the groups predictable. A few oddballs like obs_raw, obs_watch, and obs_health break the verb-first pattern, but overall naming is highly regular and easy to infer.
At 67 tools, this far exceeds the 50+ threshold for an extreme mismatch, even for a domain as complex as OBS. While each tool has a defensible place and the grouping is logical, the sheer surface area is overwhelming for an agent and the rubric is explicit about the upper bound.
The toolset provides exhaustive coverage of OBS: scenes, scene items, sources, filters, audio routing, media transport, outputs, hotkeys, projectors, replay buffer, virtual cam, recording, streaming, studio mode, and monitoring. It even handles plugin-provided functionality via hotkeys and raw websocket escape hatches. Gaps are nearly impossible to find.
Maintenance
Related MCP Connectors
An MCP server that provides congressional transcripts
An MCP server that provides tools to discover and retrieve podcast episodes transcripts.
MCP server for OpenAI API (chat completions, image generation, embeddings) via AceDataCloud
MCP server for RiverScript, an AI transcription platform - fetches transcripts shared via a link.
Related MCP Servers
- AlicenseBqualityCmaintenanceA server that provides tools to control OBS Studio remotely via the OBS WebSocket protocol, enabling management of scenes, sources, streaming, and recording through an MCP client interface.100613 npm158GPL 2.0
- AlicenseAqualityDmaintenanceAn MCP (Model Context Protocol) server for controlling OBS Studio via the built-in obs-websocket plugin. Lets Claude control your scenes, recording, streaming, audio, and more.2619 npm1MIT
- AlicenseBqualityDmaintenanceMCP server enabling LLMs to control OBS Studio through high-level show and effect APIs.187 npmMIT
- AlicenseBqualityDmaintenanceMCP server for controlling OBS Studio via obs-websocket, enabling scene switching, source management, streaming, recording, and text overlays through natural language.36114 PyPI5MIT