GetSceneItemList
Retrieve a list of scene items (sources) from a specified OBS scene to manage and automate streaming production with Presentation Buddy MCP Server.
Instructions
Gets the list of scene items (sources) in a specified scene.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| params | Yes |
Implementation Reference
- src/index.ts:453-473 (handler)Core handler logic for the GetSceneItemList MCP tool. Extracts the sceneName parameter, calls the OBS WebSocket 'GetSceneItemList' request, transforms null isGroup values to false in the response sceneItems array.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;
- src/index.ts:962-967 (handler)Response formatting specific to the GetSceneItemList tool, returning the OBS response data as a JSON string in a text content block.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) }] };
- src/index.ts:360-991 (registration)Dynamic registration loop for all MCP tools from obs_mcp_tool_def.json, including GetSceneItemList, using server.tool() with derived schema and shared handler containing switch on action.name.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:103-161 (schema)Utility function to convert MCP JSON schema definitions (expected in tool_def.json for GetSceneItemList input) into Zod validation schemas used during tool registration.function zodSchemaFromMcpSchema(mcpSchema: any): ZodType | undefined { if (!mcpSchema || !mcpSchema.type) { return undefined; } switch (mcpSchema.type) { case "object": const shape: Record<string, ZodType> = {}; if (mcpSchema.properties) { for (const key in mcpSchema.properties) { const propSchema = zodSchemaFromMcpSchema(mcpSchema.properties[key]); if (propSchema) { shape[key] = propSchema; } } } let zodObject = z.object(shape); // Zod doesn't have a direct equivalent for making only specific fields optional easily from schema alone // MCP's "required" array means fields NOT in it are optional. Zod by default makes all fields in an object required. // We would need to iterate 'properties' and if a key is not in 'mcpSchema.required', apply .optional() // For now, this simplified version assumes all defined properties are required unless explicitly made optional in a more complex mapping if (mcpSchema.required && Array.isArray(mcpSchema.required)) { // This part is tricky with Zod's builder pattern. // A more robust solution might involve constructing the object then iterating properties to add .optional() // For simplicity, we'll rely on Zod's default behavior (all fields required unless .optional() is called) // and users should define schemas carefully. } return zodObject; case "string": let zodString = z.string(); if (mcpSchema.description) zodString = zodString.describe(mcpSchema.description); // Handle enums if present if (mcpSchema.enum && Array.isArray(mcpSchema.enum)) { // z.enum requires at least one value, and TypeScript needs it to be const or string literal array // This dynamic creation is a bit tricky. For simplicity, assuming string enums. if (mcpSchema.enum.length > 0) { // Cast to [string, ...string[]] to satisfy z.enum type return z.enum(mcpSchema.enum as [string, ...string[]]); } } return zodString; case "boolean": let zodBoolean = z.boolean(); if (mcpSchema.description) zodBoolean = zodBoolean.describe(mcpSchema.description); return zodBoolean; case "integer": let zodInteger = z.number().int(); if (mcpSchema.description) zodInteger = zodInteger.describe(mcpSchema.description); return zodInteger; case "number": let zodNumber = z.number(); if (mcpSchema.description) zodNumber = zodNumber.describe(mcpSchema.description); return zodNumber; // Add other type mappings as needed (array, etc.) default: console.warn(`Unsupported MCP schema type: ${mcpSchema.type}`); return undefined; } }