/**
* Pollinations Image Service
*
* Functions and schemas for interacting with the Pollinations Image API
*/
import { createMCPResponse, createTextContent, createImageContent, buildUrl } from '../utils/coreUtils.js';
import { z } from 'zod';
// Constants
const IMAGE_API_BASE_URL = 'https://image.pollinations.ai';
/**
* Internal function to generate an image URL without MCP formatting
*
* @param {string} prompt - The text description of the image to generate
* @param {Object} options - Additional options for image generation
* @returns {Object} - Object containing the image URL and metadata
*/
async function _generateImageUrlInternal(prompt, options = {}) {
const {
model,
seed,
width = 1024,
height = 1024,
private: privateParam = true,
nologo = true,
enhance = true,
safe = false,
} = options;
// Construct the URL with query parameters
const encodedPrompt = encodeURIComponent(prompt);
const path = `prompt/${encodedPrompt}`;
const queryParams = {
model,
seed,
width,
height,
private: privateParam,
nologo,
enhance,
safe
};
const url = buildUrl(IMAGE_API_BASE_URL, path, queryParams);
// Return the URL with metadata
return {
imageUrl: url,
prompt,
width,
height,
model,
seed,
private: privateParam,
nologo,
enhance,
safe
};
}
/**
* Generates an image URL from a text prompt using the Pollinations Image API
*
* @param {Object} params - The parameters for image URL generation
* @param {string} params.prompt - The text description of the image to generate
* @param {Object} [params.options={}] - Additional options for image generation
* @param {string} [params.options.model] - Model name to use for generation
* @param {number} [params.options.seed] - Seed for reproducible results
* @param {number} [params.options.width=1024] - Width of the generated image
* @param {number} [params.options.height=1024] - Height of the generated image
* @param {boolean} [params.options.private=true] - Set to true to prevent image from appearing in public feed
* @param {boolean} [params.options.nologo=true] - Set to true to disable Pollinations logo overlay
* @param {boolean} [params.options.enhance=true] - Set to true to use an LLM to enhance the prompt with more detail
* @param {boolean} [params.options.safe=false] - Set to true for strict NSFW filtering
* @returns {Object} - MCP response object with the image URL
*/
async function generateImageUrl(params) {
const { prompt, options = {} } = params;
if (!prompt || typeof prompt !== 'string') {
throw new Error('Prompt is required and must be a string');
}
// Generate the image URL and metadata
const result = await _generateImageUrlInternal(prompt, options);
// Return the response in MCP format
return createMCPResponse([
createTextContent(result, true)
]);
}
/**
* Generates an image from a text prompt and returns the image data as base64
*
* @param {Object} params - The parameters for image generation
* @param {string} params.prompt - The text description of the image to generate
* @param {Object} [params.options={}] - Additional options for image generation
* @param {string} [params.options.model] - Model name to use for generation
* @param {number} [params.options.seed] - Seed for reproducible results
* @param {number} [params.options.width=1024] - Width of the generated image
* @param {number} [params.options.height=1024] - Height of the generated image
* @param {boolean} [params.options.private=true] - Set to true to prevent image from appearing in public feed
* @param {boolean} [params.options.nologo=true] - Set to true to disable Pollinations logo overlay
* @param {boolean} [params.options.enhance=true] - Set to true to use an LLM to enhance the prompt with more detail
* @param {boolean} [params.options.safe=false] - Set to true for strict NSFW filtering
* @returns {Promise<Object>} - MCP response object with the image data
*/
async function generateImage(params) {
const { prompt, options = {} } = params;
if (!prompt || typeof prompt !== 'string') {
throw new Error('Prompt is required and must be a string');
}
// First, generate the image URL (but don't use the MCP response format)
const urlResult = await _generateImageUrlInternal(prompt, options);
try {
// Fetch the image from the URL
const response = await fetch(urlResult.imageUrl);
if (!response.ok) {
throw new Error(`Failed to generate image: ${response.statusText}`);
}
// Get the image data as an ArrayBuffer
const imageBuffer = await response.arrayBuffer();
// Convert the ArrayBuffer to a base64 string
const base64Data = Buffer.from(imageBuffer).toString('base64');
// Determine the mime type from the response headers or default to image/jpeg
const contentType = response.headers.get('content-type') || 'image/jpeg';
const metadata = {
prompt: urlResult.prompt,
width: urlResult.width,
height: urlResult.height,
model: urlResult.model,
seed: urlResult.seed,
private: urlResult.private,
nologo: urlResult.nologo,
enhance: urlResult.enhance,
safe: urlResult.safe
};
// Return the response in MCP format
return createMCPResponse([
createImageContent(base64Data, contentType),
createTextContent(`Generated image from prompt: "${prompt}"\n\nImage metadata: ${JSON.stringify(metadata, null, 2)}`)
]);
} catch (error) {
console.error('Error generating image:', error);
throw error;
}
}
/**
* Generates an image from text prompt with optional input image for image-to-image generation
*
* @param {Object} params - The parameters for image-to-image generation
* @param {string} params.prompt - The text description of the desired output
* @param {string} params.inputImageUrl - URL of the input image to transform
* @param {Object} [params.options={}] - Additional options for image generation
* @param {string} [params.options.model] - Model name to use for generation
* @param {number} [params.options.seed] - Seed for reproducible results
* @param {number} [params.options.width=1024] - Width of the generated image
* @param {number} [params.options.height=1024] - Height of the generated image
* @param {boolean} [params.options.private=true] - Set to true to prevent image from appearing in public feed
* @param {boolean} [params.options.nologo=true] - Set to true to disable Pollinations logo overlay
* @param {boolean} [params.options.enhance=true] - Set to true to use an LLM to enhance the prompt with more detail
* @param {boolean} [params.options.safe=false] - Set to true for strict NSFW filtering
* @returns {Promise<Object>} - MCP response object with the transformed image
*/
async function generateImageToImage(params) {
const { prompt, inputImageUrl, options = {} } = params;
if (!prompt || typeof prompt !== 'string') {
throw new Error('Prompt is required and must be a string');
}
if (!inputImageUrl || typeof inputImageUrl !== 'string') {
throw new Error('Input image URL is required and must be a string');
}
const {
model,
seed,
width = 1024,
height = 1024,
private: privateParam = true,
nologo = true,
enhance = true,
safe = false,
} = options;
// Construct the URL with query parameters including the input image
const encodedPrompt = encodeURIComponent(prompt);
const path = `prompt/${encodedPrompt}`;
const queryParams = {
model,
seed,
width,
height,
private: privateParam,
nologo,
enhance,
safe,
image: inputImageUrl
};
const url = buildUrl(IMAGE_API_BASE_URL, path, queryParams);
try {
// Fetch the image from the URL
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to generate image-to-image: ${response.statusText}`);
}
// Get the image data as an ArrayBuffer
const imageBuffer = await response.arrayBuffer();
// Convert the ArrayBuffer to a base64 string
const base64Data = Buffer.from(imageBuffer).toString('base64');
// Determine the mime type from the response headers or default to image/jpeg
const contentType = response.headers.get('content-type') || 'image/jpeg';
const metadata = {
prompt,
inputImageUrl,
width,
height,
model,
seed,
private: privateParam,
nologo,
enhance,
safe
};
// Return the response in MCP format
return createMCPResponse([
createImageContent(base64Data, contentType),
createTextContent(`Generated image-to-image from prompt: "${prompt}"\n\nInput Image: ${inputImageUrl}\nImage metadata: ${JSON.stringify(metadata, null, 2)}`)
]);
} catch (error) {
console.error('Error generating image-to-image:', error);
throw error;
}
}
/**
* List available image generation models from Pollinations API
*
* @param {Object} params - The parameters for listing image models
* @returns {Promise<Object>} - MCP response object with the list of available image models
*/
async function listImageModels(params) {
try {
const url = buildUrl(IMAGE_API_BASE_URL, 'models');
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to list models: ${response.statusText}`);
}
const models = await response.json();
// Return the response in MCP format
return createMCPResponse([
createTextContent(models, true)
]);
} catch (error) {
console.error('Error listing image models:', error);
throw error;
}
}
/**
* Export tools as complete arrays ready to be passed to server.tool()
*/
export const imageTools = [
[
'generateImageUrl',
'Generate an image URL from a text prompt',
{
prompt: z.string().describe('The text description of the image to generate'),
options: z.object({
model: z.string().optional().describe('Model name to use for generation'),
seed: z.number().optional().describe('Seed for reproducible results'),
width: z.number().optional().describe('Width of the generated image'),
height: z.number().optional().describe('Height of the generated image'),
private: z.boolean().optional().describe('Set to true to prevent image from appearing in public feed (default: true)'),
nologo: z.boolean().optional().describe('Set to true to disable Pollinations logo overlay (default: true)'),
enhance: z.boolean().optional().describe('Set to true to use an LLM to enhance the prompt with more detail (default: true)'),
safe: z.boolean().optional().describe('Set to true for strict NSFW filtering (default: false)')
}).optional().describe('Additional options for image generation')
},
generateImageUrl
],
[
'generateImage',
'Generate an image and return the base64-encoded data',
{
prompt: z.string().describe('The text description of the image to generate'),
options: z.object({
model: z.string().optional().describe('Model name to use for generation'),
seed: z.number().optional().describe('Seed for reproducible results'),
width: z.number().optional().describe('Width of the generated image'),
height: z.number().optional().describe('Height of the generated image'),
private: z.boolean().optional().describe('Set to true to prevent image from appearing in public feed (default: true)'),
nologo: z.boolean().optional().describe('Set to true to disable Pollinations logo overlay (default: true)'),
enhance: z.boolean().optional().describe('Set to true to use an LLM to enhance the prompt with more detail (default: true)'),
safe: z.boolean().optional().describe('Set to true for strict NSFW filtering (default: false)')
}).optional().describe('Additional options for image generation')
},
generateImage
],
[
'generateImageToImage',
'Transform an input image using a text prompt (image-to-image generation)',
{
prompt: z.string().describe('The text description of how to transform the image'),
inputImageUrl: z.string().describe('URL of the input image to transform'),
options: z.object({
model: z.string().optional().describe('Model name to use for generation'),
seed: z.number().optional().describe('Seed for reproducible results'),
width: z.number().optional().describe('Width of the generated image (default: 1024)'),
height: z.number().optional().describe('Height of the generated image (default: 1024)'),
private: z.boolean().optional().describe('Set to true to prevent image from appearing in public feed (default: true)'),
nologo: z.boolean().optional().describe('Set to true to disable Pollinations logo overlay (default: true)'),
enhance: z.boolean().optional().describe('Set to true to use an LLM to enhance the prompt with more detail (default: true)'),
safe: z.boolean().optional().describe('Set to true for strict NSFW filtering (default: false)')
}).optional().describe('Additional options for image generation')
},
generateImageToImage
],
[
'listImageModels',
'List available image models',
{},
listImageModels
]
];