Skip to main content
Glama

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
NameRequiredDescriptionDefault
apiKeyNoOrshot API key for authentication (optional if set in environment)
templateIdYesThe ID or name of the Orshot Studio template to use
dataNoObject containing data to populate the template (e.g., dynamic content, variable replacements, URLs for images)
formatNoOutput format for the generated imagepng
responseTypeNoResponse type: base64 data, download URL, or binary dataurl
webhookNoOptional 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)}
    \`\`\``,
            },
          ],
        };
      }
    );
  • 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)}
    \`\`\``,
            },
          ],
        };
      }
  • 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"),
    },
  • 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;
      }
    }
  • 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;
      }
    }
Behavior2/5

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

With no annotations provided, the description carries the full burden of behavioral disclosure. It mentions automatic URL mapping and template ID/name flexibility, which adds some context. However, it lacks critical behavioral details such as whether this is a read-only or mutation operation, potential rate limits, error handling, or what happens when data modifications fail. For a tool that generates images with multiple parameters, this is insufficient.

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

Conciseness4/5

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

The description is appropriately concise with two sentences that front-load the core purpose. The first sentence covers the main functionality, and the second adds useful clarification about template identification. There's no wasted text, though it could be slightly more structured for clarity.

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?

Given the tool's complexity (6 parameters, no annotations, no output schema), the description is incomplete. It doesn't explain what the tool returns (e.g., image data, error responses), behavioral constraints, or how it differs from sibling tools. While the schema covers parameters well, the overall context for an AI agent to use this tool effectively is lacking.

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

Parameters3/5

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

Schema description coverage is 100%, so the schema already documents all 6 parameters thoroughly. The description adds minimal value beyond the schema by mentioning 'Automatically maps URLs to appropriate image fields' (hinting at data parameter usage) and 'template ID (numeric) or template name' (clarifying templateId). This meets the baseline of 3 since the schema does the heavy lifting, but the description doesn't significantly enhance parameter understanding.

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: 'Generate an image from an Orshot Studio template using specified data modifications.' It specifies the verb ('Generate'), resource ('image from an Orshot Studio template'), and mechanism ('using specified data modifications'). However, it doesn't explicitly differentiate from sibling tools like 'generate-image' or 'generate-image-from-library' beyond mentioning 'Studio template'.

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

Usage Guidelines3/5

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

The description implies usage context by specifying 'Orshot Studio template' and mentioning template ID or name, which suggests when to use this tool. However, it doesn't provide explicit guidance on when to choose this over alternatives like 'generate-image' or 'generate-image-from-library', nor does it mention any prerequisites or exclusions beyond the optional API key.

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/rishimohan/orshot-mcp-server'

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