Skip to main content
Glama
yshk-mrt

Presentation Buddy MCP Server

by yshk-mrt

PlayPauseMedia

Control media playback in presentations by playing, pausing, stopping, or restarting video and audio sources during streaming automation.

Instructions

Controls playback of a media source (play, pause, stop, restart). 用途: 動画クリップやオーディオの再生制御

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
paramsYes

Implementation Reference

  • The handler function for the 'PlayPauseMedia' tool. It extracts sourceName and mediaAction from parameters, converts mediaAction to uppercase, and sends a 'TriggerMediaInputAction' request to the OBS WebSocket via sendToObs. Returns success or error structured content.
    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 } };
        }
  • src/index.ts:364-991 (registration)
    Dynamic registration of tools from toolDefinitions JSON. The 'PlayPauseMedia' tool is registered here via server.tool() with its name, description, schema (derived from JSON), and a shared handler function that uses a switch on action.name to dispatch to specific logic.
        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}`);
    });
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden but provides minimal behavioral information. It mentions the actions available but doesn't disclose what 'controls playback' actually means operationally - whether it's immediate, reversible, has side effects, or requires specific permissions. For a mutation tool with zero annotation coverage, this leaves significant gaps in understanding its behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is brief but inefficiently structured. The English portion is clear but minimal, while the Japanese translation adds redundancy without value. It's front-loaded with the core purpose but wastes characters on translation rather than adding useful information about parameters or behavior.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a mutation tool with no annotations, no output schema, and 0% schema description coverage, the description is inadequate. It doesn't explain what happens after invoking the tool (success/failure indicators), doesn't cover error conditions, and leaves parameters completely unexplained. Given the complexity of media control operations, this provides insufficient context for reliable use.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has 0% description coverage, so the description must compensate but fails to do so. It doesn't explain what 'sourceName' refers to (is it a file path, source ID, or display name?) or the semantics of 'mediaAction' values beyond listing them. The schema shows 6 enum values but the description only mentions 4, creating inconsistency.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose as 'Controls playback of a media source' with specific actions listed (play, pause, stop, restart). It distinguishes itself from siblings like SetMediaTime (which controls timing) or SetAudioVolume (which controls volume) by focusing on playback state control. However, the Japanese text '用途: 動画クリップやオーディオの再生制御' is redundant and doesn't add clarity for English-speaking agents.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives. It doesn't mention when to choose PlayPauseMedia over SetMediaTime for time-based control, or how it relates to Start/StopRecording for recording operations. There's no context about prerequisites (e.g., whether a media source must exist first) or typical workflows.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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