generate-image-from-library
Generate customized images from Orshot library templates by applying text replacements, color changes, and other modifications in PNG, JPG, or PDF formats.
Instructions
Generate an image from an Orshot library template using specified modifications
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| apiKey | No | Orshot API key for authentication (optional if set in environment) | |
| templateId | Yes | The ID of the library template to use | |
| modifications | No | Object containing modifications to apply to the template (e.g., text replacements, color changes) | |
| format | No | Output format for the generated image | png |
| responseType | No | Response type: base64 data, download URL, or binary data | url |
Implementation Reference
- src/index.ts:481-640 (handler)Main execution logic for the tool: validates apiKey and templateId, makes POST request to Orshot /v1/generate/images endpoint with modifications, handles response types (url with markdown link, base64/binary as JSON), uses shared makeOrShotRequest helper with retries.const { apiKey, templateId, modifications, format, responseType } = args; // Validate template ID const templateValidation = validateTemplateId(templateId); if (!templateValidation.isValid) { return { content: [ { type: "text", text: `❌ Invalid template ID: ${templateValidation.error}`, }, ], }; } 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.", }, ], }; } // Validate API key const keyValidation = validateApiKey(actualApiKey); if (!keyValidation.isValid) { return { content: [ { type: "text", text: `❌ Invalid API key: ${keyValidation.error}`, }, ], }; } const requestBody = { templateId: templateValidation.sanitized, modifications: modifications, response: { type: responseType, format: format }, source: "orshot-mcp-server" }; const response = await makeOrShotRequest<OrShotLibraryResponse>( `${ORSHOT_API_BASE}/v1/generate/images`, { method: "POST", headers: { Authorization: `Bearer ${actualApiKey}`, }, body: JSON.stringify(requestBody), } ); if (!response) { return { content: [ { type: "text", text: "❌ Failed to generate image from library template. Please check your API key and template ID, or try again later.", }, ], }; } const { data } = response; // Debug logging to understand response structure console.error("Response debug info:", { responseType, hasData: !!response.data, dataType: typeof response.data, dataLength: response.data ? response.data.length : 0, dataPrefix: response.data ? response.data.substring(0, 20) : 'none', hasUrl: !!response.url, taskId: response.task_id, status: response.status }); // 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: `Image generated successfully from library template! 🖼️ **[View Generated Image](${response.url})** Task ID: ${response.task_id || 'Not available'} Status: ${response.status || 'Unknown'}`, }, ], }; } 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: `Image generated successfully from library template! **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: `Image generated successfully from library template! **Raw API Response:** \`\`\`json ${JSON.stringify(responseForDisplay, null, 2)} \`\`\``, }, ], }; } // Fallback to text response return { content: [ { type: "text", text: `Image generated successfully from library template! **Raw API Response:** \`\`\`json ${JSON.stringify(responseForDisplay, null, 2)} \`\`\``, }, ], }; } );
- src/index.ts:474-480 (schema)Zod input schema defining parameters for the tool: apiKey (optional), required templateId, modifications object, format (png/jpg/pdf), responseType (base64/url/binary).apiKey: z.string().optional().describe("Orshot API key for authentication (optional if set in environment)"), templateId: z.string().describe("The ID of the library template to use"), modifications: z.record(z.any()).default({}).describe("Object containing modifications to apply to the template (e.g., text replacements, color changes)"), 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"), }, async (args) => {
- src/index.ts:471-641 (registration)MCP server.tool registration call that registers the 'generate-image-from-library' tool with its description, Zod schema, and handler function."generate-image-from-library", "Generate an image from an Orshot library template using specified modifications", { apiKey: z.string().optional().describe("Orshot API key for authentication (optional if set in environment)"), templateId: z.string().describe("The ID of the library template to use"), modifications: z.record(z.any()).default({}).describe("Object containing modifications to apply to the template (e.g., text replacements, color changes)"), 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"), }, async (args) => { const { apiKey, templateId, modifications, format, responseType } = args; // Validate template ID const templateValidation = validateTemplateId(templateId); if (!templateValidation.isValid) { return { content: [ { type: "text", text: `❌ Invalid template ID: ${templateValidation.error}`, }, ], }; } 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.", }, ], }; } // Validate API key const keyValidation = validateApiKey(actualApiKey); if (!keyValidation.isValid) { return { content: [ { type: "text", text: `❌ Invalid API key: ${keyValidation.error}`, }, ], }; } const requestBody = { templateId: templateValidation.sanitized, modifications: modifications, response: { type: responseType, format: format }, source: "orshot-mcp-server" }; const response = await makeOrShotRequest<OrShotLibraryResponse>( `${ORSHOT_API_BASE}/v1/generate/images`, { method: "POST", headers: { Authorization: `Bearer ${actualApiKey}`, }, body: JSON.stringify(requestBody), } ); if (!response) { return { content: [ { type: "text", text: "❌ Failed to generate image from library template. Please check your API key and template ID, or try again later.", }, ], }; } const { data } = response; // Debug logging to understand response structure console.error("Response debug info:", { responseType, hasData: !!response.data, dataType: typeof response.data, dataLength: response.data ? response.data.length : 0, dataPrefix: response.data ? response.data.substring(0, 20) : 'none', hasUrl: !!response.url, taskId: response.task_id, status: response.status }); // 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: `Image generated successfully from library template! 🖼️ **[View Generated Image](${response.url})** Task ID: ${response.task_id || 'Not available'} Status: ${response.status || 'Unknown'}`, }, ], }; } 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: `Image generated successfully from library template! **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: `Image generated successfully from library template! **Raw API Response:** \`\`\`json ${JSON.stringify(responseForDisplay, null, 2)} \`\`\``, }, ], }; } // Fallback to text response return { content: [ { type: "text", text: `Image generated successfully from library template! **Raw API Response:** \`\`\`json ${JSON.stringify(responseForDisplay, null, 2)} \`\`\``, }, ], }; } );
- src/index.ts:165-194 (helper)Helper function to validate and sanitize the library templateId input, used in the handler.function validateTemplateId(templateId: string): { isValid: boolean; sanitized: string; error?: string } { if (!templateId || typeof templateId !== 'string') { const error = 'Template ID is required and must be a string'; logger.validation('template-id', false, error); return { isValid: false, sanitized: '', error }; } const sanitized = templateId.trim(); if (sanitized.length === 0) { const error = 'Template ID cannot be empty'; logger.validation('template-id', false, error); return { isValid: false, sanitized: '', error }; } if (sanitized.length > config.security.maxTemplateIdLength) { const error = `Template ID is too long (max ${config.security.maxTemplateIdLength} characters)`; logger.validation('template-id', false, error); return { isValid: false, sanitized: '', error }; } // Allow alphanumeric, hyphens, underscores for library templates and numbers for studio templates if (!/^[a-zA-Z0-9_-]+$/.test(sanitized)) { const error = 'Template ID contains invalid characters'; logger.validation('template-id', false, error); return { isValid: false, sanitized: '', error }; } logger.validation('template-id', true); return { isValid: true, sanitized }; }
- src/index.ts:89-162 (helper)Shared helper for making API requests to Orshot with retry logic, timeout, auth headers, and comprehensive error handling, used by the handler for the generation request.async function makeOrShotRequest<T>( url: string, options: RequestInit = {}, retries: number = config.api.retries ): Promise<T | null> { let lastError: Error | null = null; logger.time(`API Request: ${url}`); for (let attempt = 1; attempt <= retries; attempt++) { try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), config.api.timeout); const response = await fetch(url, { ...options, headers: { "Content-Type": "application/json", "User-Agent": `${config.server.name}/${config.server.version}`, ...options.headers, }, signal: controller.signal, }); clearTimeout(timeoutId); if (!response.ok) { const errorText = await response.text(); let errorMessage = `HTTP ${response.status} ${response.statusText}`; try { const errorData = JSON.parse(errorText); errorMessage = errorData.message || errorData.error || errorMessage; } catch { // Use the raw error text if JSON parsing fails errorMessage = errorText || errorMessage; } logger.apiRequest(options.method || 'GET', url, response.status); throw new Error(errorMessage); } const result = await response.json(); logger.apiRequest(options.method || 'GET', url, response.status); logger.timeEnd(`API Request: ${url}`); return result as T; } catch (error) { lastError = error instanceof Error ? error : new Error(String(error)); if (error instanceof Error && error.name === 'AbortError') { logger.error(`Request timeout on attempt ${attempt}/${retries}`, { url, timeout: config.api.timeout }); } else { logger.error(`Request failed on attempt ${attempt}/${retries}`, { url, error: lastError.message }); } // Don't retry for certain error types if (error instanceof Error && (error.message.includes('401') || error.message.includes('403') || error.message.includes('404'))) { break; } if (attempt < retries) { // Exponential backoff with configurable delay const delay = Math.pow(2, attempt - 1) * config.api.retryDelay; await new Promise(resolve => setTimeout(resolve, delay)); logger.debug(`Retrying request after ${delay}ms delay`, { url, attempt: attempt + 1, retries }); } } } logger.error("All retry attempts failed", { url, error: lastError?.message }); logger.timeEnd(`API Request: ${url}`); return null; }