GetSceneList
Retrieve a list of available OBS scenes to facilitate automated streaming setup and management within the Presentation Buddy MCP Server.
Instructions
Gets the list of available OBS scenes.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- src/index.ts:474-476 (handler)Core handler logic for the GetSceneList tool: sends 'GetSceneList' request to OBS WebSocket API with empty parameters.case "GetSceneList": obsResponseData = await sendToObs("GetSceneList", {}, context, action.name); break;
- src/index.ts:967-973 (handler)Handler response formatting specific to GetSceneList: stringifies the OBS response data as text content.}; } else if (action.name === "GetSceneList") { // For GetSceneList, return the full response as content return { content: [ { type: "text", text: JSON.stringify(obsResponseData, null, 2) } ]
- src/index.ts:360-991 (registration)Dynamic registration of MCP tools from obs_mcp_tool_def.json, including 'GetSceneList' via server.tool() with shared handler containing case-specific logic.if (toolDefinitions && toolDefinitions.actions) { toolDefinitions.actions.forEach(action => { const requestSchema = action.requestDataSchema ? zodSchemaFromMcpSchema(action.requestDataSchema) : undefined; 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}`); });
- src/index.ts:279-346 (helper)Key helper function invoked by GetSceneList handler to communicate with OBS WebSocket: handles connection check, request sending, response resolution, and errors.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 }); }
- src/index.ts:93-101 (registration)Loads obs_mcp_tool_def.json containing tool definitions (name, schema, etc.) used to register GetSceneList and other tools.const TOOL_DEF_FILE = path.join(__dirname, "obs_mcp_tool_def.json"); let toolDefinitions: { name: string, version: string, description: string, actions: any[] } = { name: 'obs-mcp-default', version: '0.0.1', description: 'OBS MCP Default', actions: []}; try { const toolDefContent = fs.readFileSync(TOOL_DEF_FILE, "utf-8"); toolDefinitions = JSON.parse(toolDefContent); } catch (error) { console.error(`Error loading tool definitions from ${TOOL_DEF_FILE}:`, error); // Proceed with empty actions or default }