Skip to main content
Glama
yshk-mrt

Presentation Buddy MCP Server

by yshk-mrt

SaveReplayBufferAndAdd

Save replay buffer content and automatically add it as a media source to a specified scene for immediate display during streaming.

Instructions

Saves the replay buffer and adds it as a media source to a scene. 用途: リプレイをすぐにシーンに表示

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
paramsYes

Implementation Reference

  • Handler implementation for the SaveReplayBufferAndAdd tool. Saves the OBS replay buffer, locates the latest replay file, and creates a new ffmpeg_source input in the specified scene using that file as a looping media source.
    case "SaveReplayBufferAndAdd":
        const replayParams = params as { sceneName: string; sourceName: string; replayFolder?: string };
        console.log(`Executing SaveReplayBufferAndAdd with params:`, replayParams);
        try {
            // 1. Save the replay buffer
            await sendToObs("SaveReplayBuffer", {}, context, action.name);
            
            // 2. Wait a bit for the file to be written to disk
            await new Promise(resolve => setTimeout(resolve, 2000));
            
            // 3. Find the latest replay file
            const defaultRecordingPath = process.env.OBS_REPLAY_PATH || path.join(os.homedir(), 'Movies');
            const replayFolder = replayParams.replayFolder || defaultRecordingPath;
            console.log(`Looking for the latest replay file in: ${replayFolder}`);
            
            // Get a list of replay files in the directory
            let files: string[] = [];
            try {
                files = fs.readdirSync(replayFolder)
                    .filter(file => file.toLowerCase().includes('replay') && (file.endsWith('.mp4') || file.endsWith('.mov')))
                    .map(file => path.join(replayFolder, file));
                    
                // Sort by modification time, newest first
                files.sort((a, b) => {
                    return fs.statSync(b).mtime.getTime() - fs.statSync(a).mtime.getTime();
                });
                
                console.log(`Found ${files.length} replay files`);
            } catch (fsError: any) {
                console.error(`Error reading replay directory ${replayFolder}:`, fsError.message);
                return { structuredContent: { success: false, error: `Failed to read replay directory: ${fsError.message}` } };
            }
            
            if (files.length === 0) {
                return { structuredContent: { success: false, error: "No replay files found" } };
            }
            
            // Get the most recent file
            const latestReplayFile = files[0];
            console.log(`Latest replay file: ${latestReplayFile}`);
            
            // 4. Create a media source with this file
            const mediaSourceResponse = await sendToObs<{ sceneItemId: number }>("CreateInput", {
                sceneName: replayParams.sceneName,
                inputName: replayParams.sourceName,
                inputKind: "ffmpeg_source",
                inputSettings: {
                    local_file: latestReplayFile,
                    looping: true
                },
                sceneItemEnabled: true
            }, context, action.name);
            
            console.log(`Created media source with ID: ${mediaSourceResponse.sceneItemId}`);
            return { 
                structuredContent: { 
                    success: true, 
                    filePath: latestReplayFile,
                    sceneItemId: mediaSourceResponse.sceneItemId 
                } 
            };
        } catch (e: any) {
            console.error(`Error in SaveReplayBufferAndAdd for OBS:`, e.message);
            return { structuredContent: { success: false, error: e.message } };
        }
  • src/index.ts:364-990 (registration)
    Dynamic registration of the SaveReplayBufferAndAdd tool (and others) from the obs_mcp_tool_def.json file using server.tool().
    server.tool(
        action.name,
        action.description || "",
        requestSchema ? { params: requestSchema } : {},
        async (params: any, context: any) => { // Using any for context if ToolContext is not easily available
            console.log(`Tool '${action.name}' called with params:`, JSON.stringify(params, null, 2));
            try {
                // Ensure OBS is connected before attempting to send a command
                if (!obsClient || obsClient.readyState !== WebSocket.OPEN) {
                    if (!isObsConnecting) {
                        console.log("OBS not connected. Attempting to connect before processing tool action.");
                        await connectToObs(); // This will throw if it fails
                        if (!obsClient || obsClient.readyState !== WebSocket.OPEN) {
                            throw new Error("Failed to connect to OBS.");
                        }
                    } else {
                        // Wait for existing connection attempt
                        await obsConnectionPromise;
                         if (!obsClient || obsClient.readyState !== WebSocket.OPEN) {
                            throw new Error("Failed to connect to OBS after waiting.");
                        }
                    }
                }
    
    
                let obsResponseData: any;
                // Map MCP actions to OBS requestTypes and params
                switch (action.name) {
                    case "SwitchScene":
                        console.log("SwitchScene params:", JSON.stringify(params, null, 2));
                        // Extract the scene name from the parameters
                        const sceneName = params.scene || (params.params && params.params.scene);
                        console.log("Extracted sceneName:", sceneName);
                        
                        if (!sceneName) {
                            throw new Error("No scene name provided");
                        }
                        
                        try {
                            // NOTE: Changed from SetCurrentProgramScene to use the direct parameters expected by OBS
                            obsResponseData = await sendToObs(
                                "SetCurrentProgramScene", 
                                { "sceneName": sceneName }, // Make sure we use the exact field name OBS expects
                                context,
                                action.name
                            );
                            console.log("OBS response:", JSON.stringify(obsResponseData, null, 2));
                        } catch (error) {
                            console.error("OBS error:", error);
                            throw error;
                        }
                        break;
                    case "StartStream":
                        obsResponseData = await sendToObs("StartStream", {}, context, action.name);
                        break;
                    case "StopStream":
                        obsResponseData = await sendToObs("StopStream", {}, context, action.name);
                        break;
                    case "StartRecording":
                        obsResponseData = await sendToObs("StartRecord", {}, context, action.name);
                        break;
                    case "StopRecording":
                        obsResponseData = await sendToObs("StopRecord", {}, context, action.name);
                        break;
                    case "SetSourceVisibility":
                        console.log("SetSourceVisibility params:", JSON.stringify(params, null, 2));
                        // Try different parameter access patterns
                        const sourceScene = params.scene || (params.params && params.params.scene);
                        const sourceName = params.source || (params.params && params.params.source);
                        const visible = params.visible !== undefined ? params.visible : 
                                       (params.params && params.params.visible !== undefined ? params.params.visible : null);
                        
                        if (!sourceScene || !sourceName || visible === null) {
                            throw new Error("Missing required parameters: scene, source, or visible");
                        }
    
                        const sceneItemIdResponse = await sendToObs<{ sceneItemId: number }>(
                            "GetSceneItemId",
                            { sceneName: sourceScene, sourceName: sourceName },
                            context,
                            action.name
                        );
                        obsResponseData = await sendToObs(
                            "SetSceneItemEnabled",
                            { sceneName: sourceScene, sceneItemId: sceneItemIdResponse.sceneItemId, sceneItemEnabled: visible },
                            context,
                            action.name
                        );
                        break;
                    case "GetSceneItemList":
                        // Extract sceneName from params
                        const sceneNameForItems = params.sceneName || params.scene || (params.params && (params.params.sceneName || params.params.scene));
                        if (!sceneNameForItems) {
                            throw new Error("Missing required parameter: sceneName");
                        }
                        obsResponseData = await sendToObs(
                            "GetSceneItemList",
                            { sceneName: sceneNameForItems },
                            context,
                            action.name
                        );
                        // Transform isGroup: null to isGroup: false to match schema
                        if (obsResponseData && Array.isArray(obsResponseData.sceneItems)) {
                            obsResponseData.sceneItems.forEach((item: any) => {
                                if (item.isGroup === null) {
                                    item.isGroup = false;
                                }
                            });
                        }
                        break;
                    case "GetSceneList":
                        obsResponseData = await sendToObs("GetSceneList", {}, context, action.name);
                        break;
                    case "SetTextContent":
                        console.log("SetTextContent params:", JSON.stringify(params, null, 2));
                        // Extract parameters
                        const textSourceName = params.source || (params.params && params.params.source);
                        const textContent = params.text || (params.params && params.params.text);
                        
                        if (!textSourceName || textContent === undefined) {
                            throw new Error("Missing required parameters: source or text");
                        }
                        
                        console.log(`Setting text content for source "${textSourceName}" to: ${textContent}`);
                        
                        try {
                            // Use SetInputSettings to update the text property
                            obsResponseData = await sendToObs(
                                "SetInputSettings",
                                { 
                                    inputName: textSourceName, 
                                    inputSettings: { 
                                        text: textContent 
                                    }
                                },
                                context,
                                action.name
                            );
                            console.log("SetTextContent response:", JSON.stringify(obsResponseData, null, 2));
                        } catch (error) {
                            console.error("Error setting text content:", error);
                            throw error;
                        }
                        break;
                    case "SetAudioMute":
                        console.log("SetAudioMute params:", JSON.stringify(params, null, 2));
                        // Extract parameters
                        const audioSourceName = params.source || (params.params && params.params.source);
                        const muteState = params.mute !== undefined ? params.mute : 
                                         (params.params && params.params.mute !== undefined ? params.params.mute : null);
                        
                        if (!audioSourceName || muteState === null) {
                            throw new Error("Missing required parameters: source or mute");
                        }
                        
                        console.log(`Setting mute state for source "${audioSourceName}" to: ${muteState}`);
                        
                        try {
                            obsResponseData = await sendToObs(
                                "SetInputMute",
                                { 
                                    inputName: audioSourceName, 
                                    inputMuted: muteState 
                                },
                                context,
                                action.name
                            );
                            console.log("SetAudioMute response:", JSON.stringify(obsResponseData, null, 2));
                        } catch (error) {
                            console.error("Error setting audio mute state:", error);
                            throw error;
                        }
                        break;
                    case "SetAudioVolume":
                        console.log("SetAudioVolume params:", JSON.stringify(params, null, 2));
                        // Extract parameters
                        const volumeSourceName = params.source || (params.params && params.params.source);
                        const volumeLevel = params.volume !== undefined ? params.volume : 
                                          (params.params && params.params.volume !== undefined ? params.params.volume : null);
                        
                        if (!volumeSourceName || volumeLevel === null) {
                            throw new Error("Missing required parameters: source or volume");
                        }
                        
                        if (volumeLevel < 0 || volumeLevel > 1) {
                            throw new Error("Volume must be between 0.0 and 1.0");
                        }
                        
                        console.log(`Setting volume for source "${volumeSourceName}" to: ${volumeLevel}`);
                        
                        try {
                            obsResponseData = await sendToObs(
                                "SetInputVolume",
                                { 
                                    inputName: volumeSourceName, 
                                    inputVolumeMul: volumeLevel  // Using multiplier (0.0 to 1.0) format
                                },
                                context,
                                action.name
                            );
                            console.log("SetAudioVolume response:", JSON.stringify(obsResponseData, null, 2));
                        } catch (error) {
                            console.error("Error setting audio volume:", error);
                            throw error;
                        }
                        break;
                    case "SetSourcePosition":
                        console.log("SetSourcePosition params:", JSON.stringify(params, null, 2));
                        // Extract parameters
                        const posSceneName = params.scene || (params.params && params.params.scene);
                        const posSourceName = params.source || (params.params && params.params.source);
                        const xPos = params.x !== undefined ? params.x : 
                                   (params.params && params.params.x !== undefined ? params.params.x : null);
                        const yPos = params.y !== undefined ? params.y : 
                                   (params.params && params.params.y !== undefined ? params.params.y : null);
                        
                        if (!posSceneName || !posSourceName || xPos === null || yPos === null) {
                            throw new Error("Missing required parameters: scene, source, x, or y");
                        }
                        
                        console.log(`Setting position for source "${posSourceName}" in scene "${posSceneName}" to: x=${xPos}, y=${yPos}`);
                        
                        try {
                            // First get the scene item ID
                            const posItemIdResponse = await sendToObs<{ sceneItemId: number }>(
                                "GetSceneItemId",
                                { sceneName: posSceneName, sourceName: posSourceName },
                                context,
                                action.name
                            );
                            
                            // Then set the position
                            obsResponseData = await sendToObs(
                                "SetSceneItemTransform",
                                { 
                                    sceneName: posSceneName, 
                                    sceneItemId: posItemIdResponse.sceneItemId,
                                    sceneItemTransform: {
                                        positionX: xPos,
                                        positionY: yPos
                                    }
                                },
                                context,
                                action.name
                            );
                            console.log("SetSourcePosition response:", JSON.stringify(obsResponseData, null, 2));
                        } catch (error) {
                            console.error("Error setting source position:", error);
                            throw error;
                        }
                        break;
                    case "SetSourceScale":
                        console.log("SetSourceScale params:", JSON.stringify(params, null, 2));
                        // Extract parameters
                        const scaleSceneName = params.scene || (params.params && params.params.scene);
                        const scaleSourceName = params.source || (params.params && params.params.source);
                        const scaleX = params.scaleX !== undefined ? params.scaleX : 
                                     (params.params && params.params.scaleX !== undefined ? params.params.scaleX : null);
                        const scaleY = params.scaleY !== undefined ? params.scaleY : 
                                     (params.params && params.params.scaleY !== undefined ? params.params.scaleY : null);
                        
                        if (!scaleSceneName || !scaleSourceName || scaleX === null || scaleY === null) {
                            throw new Error("Missing required parameters: scene, source, scaleX, or scaleY");
                        }
                        
                        console.log(`Setting scale for source "${scaleSourceName}" in scene "${scaleSceneName}" to: scaleX=${scaleX}, scaleY=${scaleY}`);
                        
                        try {
                            // First get the scene item ID
                            const scaleItemIdResponse = await sendToObs<{ sceneItemId: number }>(
                                "GetSceneItemId",
                                { sceneName: scaleSceneName, sourceName: scaleSourceName },
                                context,
                                action.name
                            );
                            
                            // Then set the scale
                            obsResponseData = await sendToObs(
                                "SetSceneItemTransform",
                                { 
                                    sceneName: scaleSceneName, 
                                    sceneItemId: scaleItemIdResponse.sceneItemId,
                                    sceneItemTransform: {
                                        scaleX: scaleX,
                                        scaleY: scaleY
                                    }
                                },
                                context,
                                action.name
                            );
                            console.log("SetSourceScale response:", JSON.stringify(obsResponseData, null, 2));
                        } catch (error) {
                            console.error("Error setting source scale:", error);
                            throw error;
                        }
                        break;
                    case "TakeSourceScreenshot":
                        // Try to get source from params or params.params
                        const mcpInputParams = params.params || params;
                        const sourceNameForScreenshot = mcpInputParams.source; // Assuming 'source' is the key from MCP
    
                        if (!sourceNameForScreenshot) {
                            console.error("TakeSourceScreenshot Error: Missing required parameter 'source'. Params received:", JSON.stringify(params, null, 2));
                            throw new Error("Missing required parameter: source");
                        }
                        
                        // Ensure sourceNameForScreenshot is a string before calling replace
                        if (typeof sourceNameForScreenshot !== 'string') {
                            console.error("TakeSourceScreenshot Error: 'source' parameter is not a string. Value:", sourceNameForScreenshot);
                            throw new Error("'source' parameter must be a string.");
                        }
    
                        console.log(`Executing TakeSourceScreenshot for source: ${sourceNameForScreenshot}`);
    
                        const format = mcpInputParams.imageFormat || "png";
                        const timestamp = Date.now();
                        const filename = `screenshot-${sourceNameForScreenshot.replace(/[^a-z0-9]/gi, '_')}-${timestamp}.${format}`;
                        const absoluteFilePath = path.join(SCREENSHOTS_DIR_ABS, filename);
                        
                        // URI that will be returned to the client
                        const resourceUri = `file://${absoluteFilePath}`;
    
                        console.log(`Attempting to save screenshot to: ${absoluteFilePath}`);
    
                        try {
                            const obsRequestData: any = {
                                sourceName: sourceNameForScreenshot, // OBS uses sourceName for SaveSourceScreenshot
                                imageFormat: format,
                                imageFilePath: absoluteFilePath, 
                            };
                            if (mcpInputParams.width !== undefined) obsRequestData.imageWidth = mcpInputParams.width;
                            if (mcpInputParams.height !== undefined) obsRequestData.imageHeight = mcpInputParams.height;
                            if (mcpInputParams.compressionQuality !== undefined) obsRequestData.imageCompressionQuality = mcpInputParams.compressionQuality;
    
                            // Use SaveSourceScreenshot OBS request
                            const obsResponse = await sendToObs("SaveSourceScreenshot", obsRequestData, context, "TakeSourceScreenshot");
                            
                            console.log(`SaveSourceScreenshot OBS response:`, obsResponse); 
                            console.log(`Screenshot for source ${sourceNameForScreenshot} saved to ${absoluteFilePath}. Returning URI: ${resourceUri}`);
                            
                            // Return the URI to the client as a resource
                            return {
                                content: [{
                                    type: "resource",
                                    resource: {
                                        uri: resourceUri,
                                        text: `Screenshot for ${sourceNameForScreenshot} available at ${filename}`,
                                        // mimeType: `image/${format}` // Optional: include mimeType if known
                                    }
                                }],
                                filename: filename // Additional top-level info
                            };
    
                        } catch (e: any) {
                            console.error(`Error in TakeSourceScreenshot for source ${sourceNameForScreenshot}:`, e.message);
                            throw new Error(`Failed to take screenshot for ${sourceNameForScreenshot}: ${e.message}`);
                        }
                        break;
                    case "SetTransitionSettings":
                        const transitionParams = params as { transitionName: string; transitionDuration: number };
                        console.log(`Executing SetTransitionSettings with params:`, transitionParams);
                        try {
                            await sendToObs("SetCurrentSceneTransition", { transitionName: transitionParams.transitionName }, context, action.name);
                            await sendToObs("SetCurrentSceneTransitionSettings", { transitionSettings: { duration: transitionParams.transitionDuration }, overlay: false }, context, action.name);
                            return { structuredContent: { success: true } };
                        } catch (e: any) {
                            console.error(`Error in SetTransitionSettings for OBS:`, e.message);
                            return { structuredContent: { success: false, error: e.message } };
                        }
                    case "TriggerStudioModeTransition":
                        console.log(`Executing TriggerStudioModeTransition`);
                        try {
                            await sendToObs("TriggerStudioModeTransition", {}, context, action.name);
                            return { structuredContent: { success: true } };
                        } catch (e: any) {
                            console.error(`Error in TriggerStudioModeTransition for OBS:`, e.message);
                            return { structuredContent: { success: false, error: e.message } };
                        }
                    case "PlayPauseMedia":
                        const mediaParams = params as { sourceName: string; mediaAction: string };
                        console.log(`Executing PlayPauseMedia with params:`, mediaParams);
                        try {
                            await sendToObs("TriggerMediaInputAction", { inputName: mediaParams.sourceName, mediaAction: mediaParams.mediaAction.toUpperCase() }, context, action.name);
                            return { structuredContent: { success: true } };
                        } catch (e: any) {
                            console.error(`Error in PlayPauseMedia for OBS:`, e.message);
                            return { structuredContent: { success: false, error: e.message } };
                        }
                    case "SetMediaTime":
                        const timeParams = params as { sourceName: string; mediaTime: number };
                        console.log(`Executing SetMediaTime with params:`, timeParams);
                        try {
                            await sendToObs("SetMediaInputCursor", { inputName: timeParams.sourceName, mediaCursor: timeParams.mediaTime }, context, action.name);
                            return { structuredContent: { success: true } };
                        } catch (e: any) {
                            console.error(`Error in SetMediaTime for OBS:`, e.message);
                            return { structuredContent: { success: false, error: e.message } };
                        }
                    case "SaveReplayBuffer":
                        console.log(`Executing SaveReplayBuffer`);
                        try {
                            await sendToObs("SaveReplayBuffer", {}, context, action.name);
                            return { structuredContent: { success: true } };
                        } catch (e: any) {
                            console.error(`Error in SaveReplayBuffer for OBS:`, e.message);
                            return { structuredContent: { success: false, error: e.message } };
                        }
                    case "SaveReplayBufferAndAdd":
                        const replayParams = params as { sceneName: string; sourceName: string; replayFolder?: string };
                        console.log(`Executing SaveReplayBufferAndAdd with params:`, replayParams);
                        try {
                            // 1. Save the replay buffer
                            await sendToObs("SaveReplayBuffer", {}, context, action.name);
                            
                            // 2. Wait a bit for the file to be written to disk
                            await new Promise(resolve => setTimeout(resolve, 2000));
                            
                            // 3. Find the latest replay file
                            const defaultRecordingPath = process.env.OBS_REPLAY_PATH || path.join(os.homedir(), 'Movies');
                            const replayFolder = replayParams.replayFolder || defaultRecordingPath;
                            console.log(`Looking for the latest replay file in: ${replayFolder}`);
                            
                            // Get a list of replay files in the directory
                            let files: string[] = [];
                            try {
                                files = fs.readdirSync(replayFolder)
                                    .filter(file => file.toLowerCase().includes('replay') && (file.endsWith('.mp4') || file.endsWith('.mov')))
                                    .map(file => path.join(replayFolder, file));
                                    
                                // Sort by modification time, newest first
                                files.sort((a, b) => {
                                    return fs.statSync(b).mtime.getTime() - fs.statSync(a).mtime.getTime();
                                });
                                
                                console.log(`Found ${files.length} replay files`);
                            } catch (fsError: any) {
                                console.error(`Error reading replay directory ${replayFolder}:`, fsError.message);
                                return { structuredContent: { success: false, error: `Failed to read replay directory: ${fsError.message}` } };
                            }
                            
                            if (files.length === 0) {
                                return { structuredContent: { success: false, error: "No replay files found" } };
                            }
                            
                            // Get the most recent file
                            const latestReplayFile = files[0];
                            console.log(`Latest replay file: ${latestReplayFile}`);
                            
                            // 4. Create a media source with this file
                            const mediaSourceResponse = await sendToObs<{ sceneItemId: number }>("CreateInput", {
                                sceneName: replayParams.sceneName,
                                inputName: replayParams.sourceName,
                                inputKind: "ffmpeg_source",
                                inputSettings: {
                                    local_file: latestReplayFile,
                                    looping: true
                                },
                                sceneItemEnabled: true
                            }, context, action.name);
                            
                            console.log(`Created media source with ID: ${mediaSourceResponse.sceneItemId}`);
                            return { 
                                structuredContent: { 
                                    success: true, 
                                    filePath: latestReplayFile,
                                    sceneItemId: mediaSourceResponse.sceneItemId 
                                } 
                            };
                        } catch (e: any) {
                            console.error(`Error in SaveReplayBufferAndAdd for OBS:`, e.message);
                            return { structuredContent: { success: false, error: e.message } };
                        }
                    case "CreateSource":
                        const sourceParams = params as { sceneName: string; sourceName: string; sourceKind: string; sourceSettings: object; setVisible?: boolean };
                        console.log(`Executing CreateSource with params:`, sourceParams);
                        try {
                            const response = await sendToObs<{ sceneItemId: number }>("CreateInput", {
                                sceneName: sourceParams.sceneName,
                                inputName: sourceParams.sourceName,
                                inputKind: sourceParams.sourceKind,
                                inputSettings: sourceParams.sourceSettings,
                                sceneItemEnabled: typeof sourceParams.setVisible === 'boolean' ? sourceParams.setVisible : true
                            }, context, action.name);
                            return { structuredContent: { success: true, sceneItemId: response.sceneItemId } };
                        } catch (e: any) {
                            console.error(`Error in CreateSource for OBS:`, e.message);
                            return { structuredContent: { success: false, error: e.message } };
                        }
                    case "SetShaderFilter":
                        const shaderParams = params as { 
                            sourceName: string; 
                            filterName: string; 
                            shaderCode?: string; 
                            shaderParameters?: Record<string, any> 
                        };
                        console.log(`Executing SetShaderFilter with params:`, shaderParams);
                        try {
                            // Directly construct filterSettings without GetSourceFilterInfo
                            const filterSettings: Record<string, any> = {};
                            
                            if (shaderParams.shaderCode) {
                                // The actual key for shader code might depend on the specific shader filter plugin.
                                // Common ones are 'shader_text', 'glsl', or 'code'.
                                // Assuming 'shader_text' based on common OBS shader filter plugins.
                                filterSettings.shader_text = shaderParams.shaderCode; 
                            }
                            
                            if (shaderParams.shaderParameters) {
                                // Shader parameters are often applied directly at the root of filterSettings
                                // or under a specific key like 'defaults'.
                                // This assumes they are direct key-value pairs.
                                for (const [key, value] of Object.entries(shaderParams.shaderParameters)) {
                                    filterSettings[key] = value;
                                }
                            }
                            
                            // Apply the constructed filter settings
                            await sendToObs(
                                "SetSourceFilterSettings", 
                                {
                                    sourceName: shaderParams.sourceName,
                                    filterName: shaderParams.filterName,
                                    filterSettings: filterSettings
                                },
                                context,
                                action.name
                            );
                            
                            console.log(`Successfully attempted to update shader filter settings for ${shaderParams.filterName} on ${shaderParams.sourceName}`);
                            return { structuredContent: { success: true } };
                        } catch (e: any) {
                            console.error(`Error in SetShaderFilter for OBS:`, e.message);
                            return { structuredContent: { success: false, error: e.message } };
                        }
                    case "SetLutFilter":
                        console.log(`Executing SetLutFilter with params:`, JSON.stringify(params, null, 2));
                        try {
                            // Parse the input parameters
                            // Check if params are directly in params or in a nested params object
                            const paramsObj = params.params || params;
                            const sourceName = paramsObj.sourceName;
                            const filterName = paramsObj.filterName;
                            const amount = paramsObj.amount;
                            const path = paramsObj.path;
                            
                            console.log(`SetLutFilter: Parsed parameters - sourceName: "${sourceName}", filterName: "${filterName}", amount: ${amount}, path: ${path || "undefined"}`);
                            
                            if (!sourceName || !filterName) {
                                throw new Error("Missing required parameters: sourceName or filterName");
                            }
                            
                            console.log(`SetLutFilter: Setting LUT filter "${filterName}" on source "${sourceName}"`);
                            
                            // Direct approach - skip getting current settings
                            console.log("Using direct filter update approach");
                            
                            const filterSettings: Record<string, any> = {};
                            
                            if (amount !== undefined) {
                                filterSettings.amount = amount;
                            }
                            
                            if (path) {
                                filterSettings.path = path;
                            }
                            
                            console.log(`Filter settings to apply:`, JSON.stringify(filterSettings, null, 2));
                            
                            const setFilterResponse = await sendToObs(
                                "SetSourceFilterSettings", 
                                {
                                    sourceName: sourceName,
                                    filterName: filterName,
                                    filterSettings: filterSettings
                                },
                                context,
                                action.name
                            );
                            
                            console.log(`SetSourceFilterSettings response:`, JSON.stringify(setFilterResponse, null, 2));
                            console.log(`Successfully updated LUT filter settings directly for ${filterName} on ${sourceName}`);
                            return { structuredContent: { success: true } };
                        } catch (e: any) {
                            console.error(`Error in SetLutFilter for OBS:`, e.message);
                            console.error(`Error details:`, e);
                            return { structuredContent: { success: false, error: e.message } };
                        }
                    default:
                        console.error(`Unknown MCP action: ${action.name}`);
                        throw new Error(`Action '${action.name}' is not implemented.`);
                }
                
                // ★★★ デバッグログ追加箇所 ★★★
                console.log(`DEBUG: Formatting response for action: '${action.name}'`);
                console.log(`DEBUG: obsResponseData content for MCP: ${JSON.stringify(obsResponseData, null, 2)}`);
                // ★★★ここまで★★★
    
                // Return the appropriate response format based on the action
                if (action.name === "GetSceneItemList") {
                    // EXPERIMENT: Return as content to test Claude client behavior
                    console.log(`DEBUG: Formatting GetSceneItemList response as 'content' for Claude client test.`);
                    return {
                        content: [{ type: "text", text: JSON.stringify(obsResponseData, null, 2) }]
                    };
                } else if (action.name === "GetSceneList") {
                    // For GetSceneList, return the full response as content
                    return {
                        content: [
                            { type: "text", text: JSON.stringify(obsResponseData, null, 2) }
                        ]
                    };
                } else {
                    // For other actions, return a simple success message
                    return {
                        content: [
                            { type: "text", text: `Action '${action.name}' executed successfully.` }
                        ]
                    };
                }
    
            } catch (error: any) {
                console.error(`Error executing tool '${action.name}':`, error);
                throw error; 
            }
        }
    );
    console.log(`Registered tool: ${action.name}`);
  • Dynamic schema generation from tool definition JSON using zodSchemaFromMcpSchema for input validation of SaveReplayBufferAndAdd tool.
    const requestSchema = action.requestDataSchema ? zodSchemaFromMcpSchema(action.requestDataSchema) : undefined;
  • Helper function sendToObs used by the SaveReplayBufferAndAdd handler to communicate with OBS WebSocket.
    async function sendToObs<T = any>(requestType: string, requestData?: any, mcpContext?: any, actionName?: string): Promise<T> {
        if (!obsClient || obsClient.readyState !== WebSocket.OPEN) {
            if (!isObsConnecting) { // Avoid multiple concurrent connection attempts from sendToObs
                console.log("OBS not connected. Attempting to connect before sending.");
                try {
                    await connectToObs(); // Attempt to connect first
                    if (!obsClient || obsClient.readyState !== WebSocket.OPEN) { // Check again after attempt
                         throw new Error("OBS WebSocket is not connected after attempt.");
                    }
                } catch (connectError) {
                     console.error("Failed to connect to OBS for sendToObs:", connectError);
                     throw new Error(`OBS Connection Error: ${(connectError as Error).message}`);
                }
            } else {
                 // If it's already connecting, we should ideally wait for obsConnectionPromise
                 // For now, throwing an error or queuing the request might be options.
                 // Let's throw, as the logic to queue and wait can get complex quickly here.
                 console.warn("OBS is currently connecting. Request will likely fail or be delayed.");
                 // Fall through to try sending, but it might fail if connection isn't ready.
            }
        }
        
        // Re-check after potential connectToObs call
        if (!obsClient || obsClient.readyState !== WebSocket.OPEN) {
            throw new Error("OBS WebSocket is not connected or ready.");
        }
    
    
        const obsRequestId = await generateRequestId();
        const payload: ObsRequest = {
            op: 6, // Request
            d: {
                requestType,
                requestId: obsRequestId,
                requestData,
            },
        };
        
        console.log("Full OBS request payload:", JSON.stringify(payload, null, 2));
    
        return new Promise((resolve, reject) => {
            if (mcpContext && actionName) { // Only store if it's an MCP-initiated request needing response mapping
                pendingObsRequests.set(obsRequestId, { resolve, reject, mcpContext, actionName });
            } else {
                // If not from MCP tool (e.g. internal calls), handle response directly or differently
                // For now, still use pendingObsRequests for simplicity
                pendingObsRequests.set(obsRequestId, { resolve, reject, mcpContext: mcpContext!, actionName: actionName || requestType });
            }
    
            // console.log("OBS TX:", JSON.stringify(payload, null, 2));
            obsClient!.send(JSON.stringify(payload), (err) => {
                if (err) {
                    console.error(`Error sending to OBS for request '${requestType}':`, err);
                    pendingObsRequests.delete(obsRequestId);
                    reject(err);
                }
            });
    
            // Timeout for OBS requests
            setTimeout(() => {
                if (pendingObsRequests.has(obsRequestId)) {
                    console.warn(`OBS request '${requestType}' (ID: ${obsRequestId}) timed out.`);
                    pendingObsRequests.get(obsRequestId)?.reject(new Error(`OBS request '${requestType}' timed out.`));
                    pendingObsRequests.delete(obsRequestId);
                }
            }, 10000); // 10 seconds timeout
        });
    }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/yshk-mrt/obs-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server