render_liquid_glass
Render a pixel-perfect Liquid Glass icon preview matching iOS 26 quality using Apple's ictool.
Instructions
Render a pixel-perfect Liquid Glass preview using Apple's ictool. Produces the exact same rendering as iOS 26. Requires Icon Composer.app installed.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| bundle_path | Yes | Path to .icon bundle | |
| output_path | Yes | Output path for the PNG file | |
| platform | No | Target platform | iOS |
| rendition | No | Appearance rendition | Default |
| width | No | Output width in pixels | |
| height | No | Output height in pixels | |
| scale | No | Scale factor (1x, 2x, 3x) | |
| light_angle | No | Light angle in degrees (0-360). Controls direction of specular highlights and shadows. | |
| tint_color | No | Tint hue (0-1) for tinted renditions | |
| tint_strength | No | Tint strength (0-1) for tinted renditions | |
| canvas_bg | No | Simple preset canvas background | |
| apple_preset | No | Apple Icon Composer preset background (overrides canvas_bg) | |
| canvas_bg_color | No | Custom hex color for canvas background | |
| canvas_bg_image | No | Path to custom background image | |
| zoom | No | Zoom level — icon size relative to canvas (1.0 = full canvas, 0.5 = half size) | |
| return_image | No | Return the rendered image inline as base64 (default true) |
Implementation Reference
- src/lib/ops-render.ts:200-256 (handler)The main handler function for the render_liquid_glass tool. Checks for ictool availability, renders via Apple's Icon Composer ictool CLI, applies optional canvas backgrounds and zoom, and returns the result as text + optional base64 image.
export async function renderLiquidGlass(params: RenderLiquidGlassParams): Promise<McpResult> { try { if (!await ictoolAvailable()) { return { content: [{ type: 'text', text: 'Error: Icon Composer.app not found at /Applications/Icon Composer.app. Install it from developer.apple.com/icon-composer/' }], isError: true, }; } await renderWithIctool({ bundlePath: params.bundle_path, outputPath: params.output_path, platform: params.platform, rendition: params.rendition, width: params.width, height: params.height, scale: params.scale, lightAngle: params.light_angle, tintColor: params.tint_color, tintStrength: params.tint_strength, }); const hasBackground = params.canvas_bg_image || params.canvas_bg_color || params.apple_preset || (params.canvas_bg && params.canvas_bg !== 'none'); if (CLEAR_RENDITIONS.has(params.rendition) && hasBackground) { return { content: [{ type: 'text', text: `ClearLight/ClearDark renditions do not support canvas backgrounds. Apple's glass transparency effect requires a Metal GPU pipeline that isn't available via CLI. Use Default, Dark, or Tinted renditions for background compositing.` }], isError: true, }; } const canvasBg = resolveCanvasBackgroundParam(params); if (canvasBg.type !== 'none' || params.zoom !== 1.0) { const iconBuffer = await fs.readFile(params.output_path); const canvasSize = Math.max(params.width, params.height); const iconSize = Math.round(canvasSize * params.zoom); const result = await compositeOnBackground(iconBuffer, canvasBg, canvasSize, iconSize); await fs.writeFile(params.output_path, result); } const stat = await fs.stat(params.output_path); const content: McpContentBlock[] = [ { type: 'text', text: `Rendered Liquid Glass preview to ${params.output_path} (${params.width}x${params.height}@${params.scale}x, ${params.rendition}, zoom: ${params.zoom}x, ${(stat.size / 1024).toFixed(1)} KB)` }, ]; if (params.return_image !== false) { const fileBuffer = await fs.readFile(params.output_path); if (fileBuffer.length <= MAX_INLINE_IMAGE_BYTES) { content.push({ type: 'image', data: fileBuffer.toString('base64'), mimeType: 'image/png' }); } } return { content }; } catch (error: unknown) { const msg = error instanceof Error ? error.message : 'Unknown error'; return { content: [{ type: 'text', text: `Error: ${msg}` }], isError: true }; } } - src/lib/ops-render.ts:30-47 (schema)TypeScript interface RenderLiquidGlassParams defining all input parameters: bundle_path, output_path, platform, rendition, width, height, scale, light_angle, tint_color, tint_strength, canvas_bg, apple_preset, canvas_bg_color, canvas_bg_image, zoom, return_image.
export interface RenderLiquidGlassParams { bundle_path: string; output_path: string; platform: string; rendition: string; width: number; height: number; scale: number; light_angle?: number; tint_color?: number; tint_strength?: number; canvas_bg?: string; apple_preset?: string; canvas_bg_color?: string; canvas_bg_image?: string; zoom: number; return_image?: boolean; } - src/server.ts:234-257 (registration)MCP server tool registration for 'render_liquid_glass' using server.tool(), with Zod schema defining all parameters and the handler delegating to renderLiquidGlass().
// ── Tool: render_liquid_glass ── server.tool( 'render_liquid_glass', 'Render a pixel-perfect Liquid Glass preview using Apple\'s ictool. Produces the exact same rendering as iOS 26. Requires Icon Composer.app installed.', { bundle_path: z.string().describe('Path to .icon bundle'), output_path: z.string().describe('Output path for the PNG file'), platform: z.enum(['iOS', 'macOS', 'watchOS']).default('iOS').describe('Target platform'), rendition: z.enum(['Default', 'Dark', 'TintedLight', 'TintedDark', 'ClearLight', 'ClearDark']).default('Default').describe('Appearance rendition'), width: z.number().min(16).max(2048).default(1024).describe('Output width in pixels'), height: z.number().min(16).max(2048).default(1024).describe('Output height in pixels'), scale: z.number().min(1).max(3).default(1).describe('Scale factor (1x, 2x, 3x)'), light_angle: z.optional(z.number().min(0).max(360)).describe('Light angle in degrees (0-360). Controls direction of specular highlights and shadows.'), tint_color: z.optional(z.number().min(0).max(1)).describe('Tint hue (0-1) for tinted renditions'), tint_strength: z.optional(z.number().min(0).max(1)).describe('Tint strength (0-1) for tinted renditions'), canvas_bg: z.optional(z.enum(['none', 'light', 'dark', 'checkerboard', 'homescreen-light', 'homescreen-dark'])).describe('Simple preset canvas background'), apple_preset: z.optional(z.enum(['sine-purple-orange', 'sine-gasflame', 'sine-magenta', 'sine-green-yellow', 'sine-purple-orange-black', 'sine-gray'])).describe('Apple Icon Composer preset background (overrides canvas_bg)'), canvas_bg_color: z.optional(z.string()).describe('Custom hex color for canvas background'), canvas_bg_image: z.optional(z.string()).describe('Path to custom background image'), zoom: z.number().min(0.1).max(3.0).default(1.0).describe('Zoom level — icon size relative to canvas (1.0 = full canvas, 0.5 = half size)'), return_image: z.boolean().default(true).describe('Return the rendered image inline as base64 (default true)'), }, async (params) => renderLiquidGlass(params), ); - src/lib/ops-render.ts:49-65 (helper)resolveCanvasBackgroundParam helper function used by renderLiquidGlass to parse canvas background parameters (image, color, apple preset, or preset) into a CanvasBackground object.
export function resolveCanvasBackgroundParam(params: { canvas_bg_image?: string; canvas_bg_color?: string; apple_preset?: string; canvas_bg?: string; }): CanvasBackground { if (params.canvas_bg_image) { return { type: 'image', path: params.canvas_bg_image }; } else if (params.canvas_bg_color) { return { type: 'solid', color: params.canvas_bg_color }; } else if (params.apple_preset) { return { type: 'apple-preset', name: params.apple_preset as ApplePresetName }; } else if (params.canvas_bg && params.canvas_bg !== 'none') { return { type: 'preset', name: params.canvas_bg as any }; } return { type: 'none' }; } - src/cli.ts:332-370 (registration)CLI command registration for 'render' which calls renderLiquidGlass with parsed command-line options.
program .command('render') .description('Render Liquid Glass via ictool') .argument('<bundle_path>', 'Path to the .icon bundle') .argument('<output_path>', 'Output image path') .option('--platform <name>', 'Platform (iOS, macOS, watchOS)', 'iOS') .option('--rendition <name>', 'Rendition name', 'Default') .option('--width <n>', 'Output width', toInt, 1024) .option('--height <n>', 'Output height', toInt, 1024) .option('--scale <n>', 'Scale factor', toInt, 1) .option('--light-angle <n>', 'Light angle', toFloat) .option('--tint-color <n>', 'Tint color', toFloat) .option('--tint-strength <n>', 'Tint strength', toFloat) .option('--canvas-bg <preset>', 'Canvas background preset') .option('--apple-preset <name>', 'Apple wallpaper preset name') .option('--canvas-bg-color <hex>', 'Canvas background solid color') .option('--canvas-bg-image <path>', 'Canvas background image path') .option('--zoom <n>', 'Zoom factor', toFloat, 1.0) .action(async (bundle_path, output_path, opts) => { await run(() => renderLiquidGlass({ bundle_path, output_path, platform: opts.platform, rendition: opts.rendition, width: opts.width, height: opts.height, scale: opts.scale, light_angle: opts.lightAngle, tint_color: opts.tintColor, tint_strength: opts.tintStrength, canvas_bg: opts.canvasBg, apple_preset: opts.applePreset, canvas_bg_color: opts.canvasBgColor, canvas_bg_image: opts.canvasBgImage, zoom: opts.zoom, }), ); });