generate-image-from-studio
Create customized images from Orshot Studio templates by applying data modifications and automatically mapping URLs to image fields.
Instructions
Generate an image from an Orshot Studio template using specified data modifications. Automatically maps URLs to appropriate image fields in the template. You can use either the template ID (numeric) or template name.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| apiKey | No | Orshot API key for authentication (optional if set in environment) | |
| templateId | Yes | The ID or name of the Orshot Studio template to use | |
| data | No | Object containing data to populate the template (e.g., dynamic content, variable replacements, URLs for images) | |
| format | No | Output format for the generated image | png |
| responseType | No | Response type: base64 data, download URL, or binary data | url |
| webhook | No | Optional webhook URL to receive notifications when the rendering is complete |
Implementation Reference
- src/index.ts:643-797 (registration)MCP server.tool registration for the 'generate-image-from-studio' tool. Includes tool name, description, Zod input schema (apiKey, templateId, data/modifications, format, responseType, webhook), and the complete handler function.server.tool( "generate-image-from-studio", "Generate an image from an Orshot Studio template using specified data modifications. Automatically maps URLs to appropriate image fields in the template. You can use either the template ID (numeric) or template name.", { apiKey: z.string().optional().describe("Orshot API key for authentication (optional if set in environment)"), templateId: z.string().describe("The ID or name of the Orshot Studio template to use"), data: z.record(z.any()).default({}).describe("Object containing data to populate the template (e.g., dynamic content, variable replacements, URLs for images)"), format: z.enum(["png", "jpg", "pdf"]).default("png").describe("Output format for the generated image"), responseType: z.enum(["base64", "url", "binary"]).default("url").describe("Response type: base64 data, download URL, or binary data"), webhook: z.string().url().optional().describe("Optional webhook URL to receive notifications when the rendering is complete"), }, async (args) => { const { apiKey, templateId, data, format, responseType, webhook } = args; const actualApiKey = apiKey || DEFAULT_API_KEY; if (!actualApiKey) { return { content: [ { type: "text", text: "No API key provided. Please provide an API key parameter or set ORSHOT_API_KEY environment variable.", }, ], }; } // Resolve template ID from name if needed const resolvedTemplateId = await resolveStudioTemplateId(templateId, actualApiKey); if (!resolvedTemplateId) { return { content: [ { type: "text", text: `ā Studio template "${templateId}" not found. Please check the template ID or name.`, }, ], }; } // Auto-map modifications based on template structure const mappedData = await autoMapModifications(resolvedTemplateId, data, actualApiKey); const requestBody: any = { templateId: resolvedTemplateId, modifications: mappedData, response: { type: responseType, format: format }, source: "orshot-mcp-server" }; if (webhook) { requestBody.webhook = webhook; } const response = await makeOrShotRequest<OrShotStudioResponse>( `${ORSHOT_API_BASE}/v1/studio/render`, { method: "POST", headers: { Authorization: `Bearer ${actualApiKey}`, }, body: JSON.stringify(requestBody), } ); if (!response) { return { content: [ { type: "text", text: "Failed to generate image from studio template. Please check your API key and template ID.", }, ], }; } const { data: responseData } = response; // Create raw response display (truncate data for readability) const responseForDisplay = { ...response, data: response.data ? (response.data.length > 100 ? `${response.data.substring(0, 100)}... (truncated, total length: ${response.data.length})` : response.data) : response.data }; // Handle different response types if (responseType === "url" && response.url) { // Default case: Return clickable "View Generated Image" link for URL responses return { content: [ { type: "text", text: `Studio image generated successfully! š¼ļø **[View Generated Image](${response.url})** Task ID: ${response.task_id || 'Not available'} Status: ${response.status || 'Unknown'} ${webhook ? `Webhook notifications will be sent to: ${webhook}` : ""}`, }, ], }; } else if (responseType === "base64" && response.data && typeof response.data === 'string' && response.data.startsWith('data:image/')) { // Return the raw JSON for base64 responses (with truncated data) return { content: [ { type: "text", text: `Studio image generated successfully! **Raw API Response:** \`\`\`json ${JSON.stringify(responseForDisplay, null, 2)} \`\`\``, }, ], }; } else if (responseType === "binary") { // Return the raw JSON for binary responses return { content: [ { type: "text", text: `Studio image generated successfully! **Raw API Response:** \`\`\`json ${JSON.stringify(responseForDisplay, null, 2)} \`\`\``, }, ], }; } // Fallback to text response return { content: [ { type: "text", text: `Studio image generated successfully! **Raw API Response:** \`\`\`json ${JSON.stringify(responseForDisplay, null, 2)} \`\`\``, }, ], }; } );
- src/index.ts:654-796 (handler)Handler function that validates API key and template, resolves studio template ID (supports names), auto-maps input data to template fields (esp. URLs to images), calls POST /v1/studio/render API, processes response (success/error, different types: url with markdown link, base64/binary as JSON).async (args) => { const { apiKey, templateId, data, format, responseType, webhook } = args; const actualApiKey = apiKey || DEFAULT_API_KEY; if (!actualApiKey) { return { content: [ { type: "text", text: "No API key provided. Please provide an API key parameter or set ORSHOT_API_KEY environment variable.", }, ], }; } // Resolve template ID from name if needed const resolvedTemplateId = await resolveStudioTemplateId(templateId, actualApiKey); if (!resolvedTemplateId) { return { content: [ { type: "text", text: `ā Studio template "${templateId}" not found. Please check the template ID or name.`, }, ], }; } // Auto-map modifications based on template structure const mappedData = await autoMapModifications(resolvedTemplateId, data, actualApiKey); const requestBody: any = { templateId: resolvedTemplateId, modifications: mappedData, response: { type: responseType, format: format }, source: "orshot-mcp-server" }; if (webhook) { requestBody.webhook = webhook; } const response = await makeOrShotRequest<OrShotStudioResponse>( `${ORSHOT_API_BASE}/v1/studio/render`, { method: "POST", headers: { Authorization: `Bearer ${actualApiKey}`, }, body: JSON.stringify(requestBody), } ); if (!response) { return { content: [ { type: "text", text: "Failed to generate image from studio template. Please check your API key and template ID.", }, ], }; } const { data: responseData } = response; // Create raw response display (truncate data for readability) const responseForDisplay = { ...response, data: response.data ? (response.data.length > 100 ? `${response.data.substring(0, 100)}... (truncated, total length: ${response.data.length})` : response.data) : response.data }; // Handle different response types if (responseType === "url" && response.url) { // Default case: Return clickable "View Generated Image" link for URL responses return { content: [ { type: "text", text: `Studio image generated successfully! š¼ļø **[View Generated Image](${response.url})** Task ID: ${response.task_id || 'Not available'} Status: ${response.status || 'Unknown'} ${webhook ? `Webhook notifications will be sent to: ${webhook}` : ""}`, }, ], }; } else if (responseType === "base64" && response.data && typeof response.data === 'string' && response.data.startsWith('data:image/')) { // Return the raw JSON for base64 responses (with truncated data) return { content: [ { type: "text", text: `Studio image generated successfully! **Raw API Response:** \`\`\`json ${JSON.stringify(responseForDisplay, null, 2)} \`\`\``, }, ], }; } else if (responseType === "binary") { // Return the raw JSON for binary responses return { content: [ { type: "text", text: `Studio image generated successfully! **Raw API Response:** \`\`\`json ${JSON.stringify(responseForDisplay, null, 2)} \`\`\``, }, ], }; } // Fallback to text response return { content: [ { type: "text", text: `Studio image generated successfully! **Raw API Response:** \`\`\`json ${JSON.stringify(responseForDisplay, null, 2)} \`\`\``, }, ], }; }
- src/index.ts:646-653 (schema)Zod schema defining input parameters for the tool, including optional API key, template ID/name, data object for modifications, output format, response type, and optional webhook.{ apiKey: z.string().optional().describe("Orshot API key for authentication (optional if set in environment)"), templateId: z.string().describe("The ID or name of the Orshot Studio template to use"), data: z.record(z.any()).default({}).describe("Object containing data to populate the template (e.g., dynamic content, variable replacements, URLs for images)"), format: z.enum(["png", "jpg", "pdf"]).default("png").describe("Output format for the generated image"), responseType: z.enum(["base64", "url", "binary"]).default("url").describe("Response type: base64 data, download URL, or binary data"), webhook: z.string().url().optional().describe("Optional webhook URL to receive notifications when the rendering is complete"), },
- src/index.ts:431-467 (helper)Helper function to resolve studio template name to its numeric ID by querying /v1/studio/templates endpoint. Used at line 670 in handler.async function resolveStudioTemplateId(templateIdOrName: string, apiKey: string): Promise<string | null> { try { // If it's already a numeric ID, return as-is if (isLikelyStudioTemplate(templateIdOrName)) { return templateIdOrName; } // Fetch studio templates to find by name const studioResponse = await fetch(`${ORSHOT_API_BASE}/v1/studio/templates`, { headers: { "Authorization": `Bearer ${apiKey}`, "Content-Type": "application/json", }, }); if (studioResponse.ok) { const studioTemplates = await studioResponse.json(); const templatesArray = Array.isArray(studioTemplates) ? studioTemplates : []; // Find template by name (case-insensitive) or exact ID match const matchedTemplate = templatesArray.find((template: any) => template.name?.toLowerCase() === templateIdOrName.toLowerCase() || template.id === templateIdOrName ); if (matchedTemplate) { logger.debug(`Resolved template name "${templateIdOrName}" to ID: ${matchedTemplate.id}`); return String(matchedTemplate.id); } } return null; // Template not found } catch (error) { logger.error("Error resolving studio template ID", { templateIdOrName, error: error instanceof Error ? error.message : String(error) }); return null; } }
- src/index.ts:220-339 (helper)Key helper for auto-mapping user-provided data/URLs to correct template modification fields based on template structure. Unique feature for studio templates, called at line 683.async function autoMapModifications(templateId: string, inputModifications: Record<string, any>, apiKey: string): Promise<Record<string, any>> { if (!config.features.autoMapping) { logger.debug("Auto-mapping is disabled, returning input as-is"); return inputModifications; } try { logger.debug(`Starting auto-mapping for template ${templateId}`); // Get template modifications to understand the expected fields const response = await fetch(`${ORSHOT_API_BASE}/v1/studio/template/modifications?templateId=${templateId}`, { method: "GET", headers: { "Authorization": `Bearer ${apiKey}`, "Content-Type": "application/json", }, }); if (!response.ok) { logger.warn("Failed to fetch template modifications for auto-mapping", { templateId, status: response.status }); return inputModifications; } const modifications = await response.json(); const modArray = Array.isArray(modifications) ? modifications : []; if (modArray.length === 0) { logger.warn("No modifications found for template, using input as-is", { templateId }); return inputModifications; } logger.debug(`Found ${modArray.length} template modifications`, { templateId, modifications: modArray.map((m: any) => ({ key: m.key || m.id, description: m.description })) }); // Create a mapping object const mappedModifications = { ...inputModifications }; // Helper function to check if a string is a URL const isUrl = (str: string) => { try { const url = new URL(str); return url.protocol === 'https:' || url.protocol === 'http:'; } catch { return false; } }; // Look for URL patterns in input and map to appropriate fields for (const [key, value] of Object.entries(inputModifications)) { if (typeof value === 'string' && isUrl(value)) { // This is a URL - find the best matching modification field const urlMod = modArray.find((mod: any) => { const modKey = (mod.key || mod.id || '').toLowerCase(); const modDesc = (mod.description || '').toLowerCase(); return ( modKey.includes('image') || modKey.includes('url') || modKey.includes('photo') || modKey.includes('picture') || modKey.includes('media') || modKey.includes('src') || modDesc.includes('image') || modDesc.includes('url') || modDesc.includes('photo') || modDesc.includes('picture') || modDesc.includes('media') ); }); if (urlMod) { const urlKey = urlMod.key || urlMod.id; if (urlKey) { // Remove the original key if it was a generic name and add the proper field if (key !== urlKey) { delete mappedModifications[key]; } mappedModifications[urlKey] = value; logger.debug(`Auto-mapped URL to field`, { url: value, field: urlKey, templateId }); } } } } // Special handling: if we have a single URL value but no clear field mapping, // and there's only one image-like field, use that const urlValues = Object.values(inputModifications).filter(v => typeof v === 'string' && isUrl(v)); const imageFields = modArray.filter((mod: any) => { const modKey = (mod.key || mod.id || '').toLowerCase(); const modDesc = (mod.description || '').toLowerCase(); return modKey.includes('image') || modKey.includes('photo') || modKey.includes('picture') || modKey.includes('media') || modKey.includes('url') || modKey.includes('src') || modDesc.includes('image') || modDesc.includes('photo') || modDesc.includes('picture'); }); if (urlValues.length === 1 && imageFields.length === 1) { const urlValue = urlValues[0]; const imageField = imageFields[0]; const fieldKey = imageField.key || imageField.id; if (fieldKey && !mappedModifications[fieldKey]) { mappedModifications[fieldKey] = urlValue; logger.debug(`Auto-mapped single URL to single image field`, { url: urlValue, field: fieldKey, templateId }); } } const mappedFields = Object.keys(mappedModifications).filter(key => key !== Object.keys(inputModifications).find(k => mappedModifications[key] === inputModifications[k])); if (mappedFields.length > 0) { logger.autoMapping(templateId, mappedFields); } logger.debug("Auto-mapping completed", { templateId, mappedModifications }); return mappedModifications; } catch (error) { logger.error("Error in auto-mapping modifications", { templateId, error: error instanceof Error ? error.message : String(error) }); return inputModifications; } }