Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ Open that file in an MCPB-compatible desktop client. The package prompts for the
| `OBS_WEBSOCKET_URL` | No | `ws://localhost:4455` | Address of the OBS WebSocket server |
| `OBS_WEBSOCKET_PASSWORD` | Only when OBS authentication is enabled | None | Password configured in OBS |
| `OBS_MCP_MAX_SCREENSHOT_BYTES` | No | `4194304` | Maximum decoded size of a screenshot returned through MCP; the hard ceiling is 6 MiB |
| `OBS_MCP_TOOLSETS` | No | all tools | Comma-separated tool groups to register: `all`, `core`, or any group listed under [Choosing tools](#choosing-tools) |
| `OBS_MCP_TOOLS` | No | none | Comma-separated tool names to register in addition to `OBS_MCP_TOOLSETS` |
| `OBS_MCP_READ_ONLY` | No | `false` | Register only read-only tools; overrides both lists |
| `OBS_MCP_LOG_LEVEL` | No | `info` | Diagnostic output on stderr: `debug`, `info`, `error`, or `silent` |

The server never prints the password. Connection and protocol diagnostics are written to stderr so stdout remains reserved for MCP messages.
Expand All @@ -112,8 +115,33 @@ The 156 tools are organized around the OBS protocol rather than a smaller opinio
- a recording preflight (`obs-preflight`) that catches the conditions under which OBS silently refuses or botches a recording
- protocol description and the guarded generic request fallback

Tools that send a single OBS request declare an `outputSchema` generated from the pinned protocol, so clients receive typed `structuredContent` with the documented response fields. Fields are optional and nullable because OBS omits newer fields in older versions and returns undocumented nulls.

Tool discovery works without an OBS connection. Calls that need OBS return an MCP error until the WebSocket connection is ready.

### Choosing tools

Every tool is registered by default. Clients load each registered tool's definition into the model's context, so a smaller set is cheaper and easier for the model to choose from.

| Group | Covers |
|---|---|
| `general` | status, version, statistics, hotkeys, vendor requests, custom events, sleep |
| `scenes` | scenes, canvases, program and preview scene |
| `scene-items` | scene items, groups, transforms, ordering, locking, blend modes |
| `sources` | source screenshots and active state |
| `inputs` | inputs, input settings and properties, audio, deinterlacing |
| `media` | media input playback |
| `filters` | source filters |
| `transitions` | transitions, overrides, the T-Bar |
| `record` | recording, chapters, and `obs-preflight` |
| `stream` | streaming and captions |
| `outputs` | virtual camera, replay buffer, and generic outputs |
| `config` | profiles, scene collections, video settings, persistent data |
| `ui` | studio mode, dialogs, projectors |
| `protocol` | `obs-describe-request` and the generic `obs-call-request` |

`core` expands to `general`, `scenes`, `scene-items`, `sources`, `inputs`, and `record` (79 tools). `OBS_MCP_TOOLSETS=core OBS_MCP_READ_ONLY=true` gives a 38-tool inspection-only server. The server refuses to start when a group or tool name is unknown.

## Where it fits

OBS MCP is well suited to operational work that already has a clear outcome: inspecting a scene collection, creating or arranging sources, changing audio state, switching scenes, taking screenshots, checking output status, or carrying out a repeatable recording setup.
Expand Down
37 changes: 33 additions & 4 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,20 @@
"support": "https://github.com/Tom-R-Main/OBS-MCP/issues",
"license": "GPL-2.0-only",
"tools_generated": true,
"keywords": ["obs", "streaming", "recording", "video", "broadcast", "scenes"],
"keywords": [
"obs",
"streaming",
"recording",
"video",
"broadcast",
"scenes"
],
"compatibility": {
"platforms": ["darwin", "win32", "linux"],
"platforms": [
"darwin",
"win32",
"linux"
],
"runtimes": {
"node": ">=20.19.0"
}
Expand All @@ -31,10 +42,14 @@
"entry_point": "server/index.js",
"mcp_config": {
"command": "node",
"args": ["${__dirname}/server/index.js"],
"args": [
"${__dirname}/server/index.js"
],
"env": {
"OBS_WEBSOCKET_URL": "${user_config.obs_websocket_url}",
"OBS_WEBSOCKET_PASSWORD": "${user_config.obs_websocket_password}"
"OBS_WEBSOCKET_PASSWORD": "${user_config.obs_websocket_password}",
"OBS_MCP_TOOLSETS": "${user_config.obs_mcp_toolsets}",
"OBS_MCP_READ_ONLY": "${user_config.obs_mcp_read_only}"
}
}
},
Expand All @@ -52,6 +67,20 @@
"description": "Password from OBS WebSocket Server Settings. Leave empty if authentication is disabled.",
"required": false,
"sensitive": true
},
"obs_mcp_toolsets": {
"type": "string",
"title": "Tool groups",
"description": "Comma-separated tool groups to expose: all, core, or any of general, scenes, scene-items, sources, inputs, media, filters, transitions, record, stream, outputs, config, ui, protocol.",
"required": false,
"default": "all"
},
"obs_mcp_read_only": {
"type": "boolean",
"title": "Read-only mode",
"description": "Expose only tools that do not change OBS.",
"required": false,
"default": false
}
}
}
23 changes: 22 additions & 1 deletion src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { McpServer } from "@modelcontextprotocol/server";
import { OBSWebSocketClient } from "./client.js";
import * as tools from "./tools/index.js";
import { PACKAGE_VERSION } from "./version.js";
import { ALL_TOOLS, assertKnownTools, parseToolFilter, type ToolFilter } from "./tools/toolsets.js";
import { logger } from "./logger.js";

// Create the OBS WebSocket client
Expand Down Expand Up @@ -91,19 +92,39 @@ obsClient.on("disconnected", () => {
}
});

// Parsed once at startup so a bad OBS_MCP_TOOLSETS/OBS_MCP_TOOLS value fails fast.
let toolFilter: ToolFilter | null = null;

export function createServer(): McpServer {
const server = new McpServer({
name: "obs-mcp",
version: PACKAGE_VERSION,
});

tools.initialize(server, obsClient);
tools.initialize(server, obsClient, toolFilter ?? ALL_TOOLS);
return server;
}

function loadToolFilter(): ToolFilter {
const filter = parseToolFilter();
const registry = tools.initialize(
new McpServer({ name: "obs-mcp-validation", version: PACKAGE_VERSION }),
obsClient,
filter,
);
assertKnownTools(filter, registry);
const count = registry.filter(({ registered }) => registered).length;
if (count === 0) throw new Error("The tool filter excludes every tool");
if (filter.groups !== null || filter.readOnly) {
logger.log(`Registering ${count} of ${registry.length} tools${filter.readOnly ? " (read-only)" : ""}`);
}
return filter;
}

// Set up server startup logic
export async function startServer(): Promise<void> {
try {
toolFilter = loadToolFilter();
stdioServer = serveStdio(createServer, {
onerror: (error) => logger.error(`MCP stdio error: ${error.message}`),
});
Expand Down
Loading
Loading