upload_image
Upload an image to ComfyUI for use in img2img, ControlNet, or IP-Adapter workflows. Accepts a source URL or base64-encoded data, returns the stored filename for LoadImage nodes.
Instructions
Upload a reference image to ComfyUI for use in img2img, ControlNet, or IP-Adapter workflows. Accepts either a source URL (will be fetched) or base64-encoded image data. Returns the stored filename for use as 'image' in workflow nodes like LoadImage.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| source_url | No | URL to fetch the image from. One of source_url or image_base64 is required. | |
| image_base64 | No | Base64-encoded image data (without the data:image/... prefix). One of source_url or image_base64 is required. | |
| filename | No | Filename to save as on the ComfyUI side. Defaults to a timestamped name. | |
| overwrite | No | Replace an existing file with the same name |
Implementation Reference
- src/tools/images.ts:36-57 (handler)The tool handler function for "upload_image". It accepts source_url or image_base64, delegates to client.fetchAndUploadImage or client.uploadImage, and returns the uploaded filename/subfolder/type.
async (args) => { if (!args.source_url && !args.image_base64) { throw new Error("Must provide either source_url or image_base64"); } const result = args.source_url ? await client.fetchAndUploadImage(args.source_url, args.filename) : await client.uploadImage( Buffer.from(args.image_base64!, "base64"), args.filename ?? `upload-${Date.now()}.png`, { overwrite: args.overwrite }, ); return { content: [ { type: "text" as const, text: `Uploaded: ${result.name} (subfolder: ${result.subfolder || "(root)"}, type: ${result.type})`, }, ], }; }, - src/tools/images.ts:6-26 (schema)Zod schema for upload_image tool parameters: source_url, image_base64, filename, and overwrite.
const uploadImageSchema = { source_url: z .string() .url() .optional() .describe("URL to fetch the image from. One of source_url or image_base64 is required."), image_base64: z .string() .optional() .describe( "Base64-encoded image data (without the data:image/... prefix). One of source_url or image_base64 is required.", ), filename: z .string() .optional() .describe("Filename to save as on the ComfyUI side. Defaults to a timestamped name."), overwrite: z .boolean() .default(false) .describe("Replace an existing file with the same name"), }; - src/tools/images.ts:28-58 (registration)The registerImageTools function that calls server.tool('upload_image', ...) to register the tool.
export function registerImageTools( server: McpServer, client: ComfyUIClient, ): void { server.tool( "upload_image", "Upload a reference image to ComfyUI for use in img2img, ControlNet, or IP-Adapter workflows. Accepts either a source URL (will be fetched) or base64-encoded image data. Returns the stored filename for use as 'image' in workflow nodes like LoadImage.", uploadImageSchema, async (args) => { if (!args.source_url && !args.image_base64) { throw new Error("Must provide either source_url or image_base64"); } const result = args.source_url ? await client.fetchAndUploadImage(args.source_url, args.filename) : await client.uploadImage( Buffer.from(args.image_base64!, "base64"), args.filename ?? `upload-${Date.now()}.png`, { overwrite: args.overwrite }, ); return { content: [ { type: "text" as const, text: `Uploaded: ${result.name} (subfolder: ${result.subfolder || "(root)"}, type: ${result.type})`, }, ], }; }, ); - src/server.ts:11-11 (registration)Import of registerImageTools from ./tools/images.js.
import { registerImageTools } from "./tools/images.js"; - src/server.ts:46-46 (registration)Invocation of registerImageTools(s, client) to register the tool on the MCP server.
registerImageTools(s, client); - src/comfyui/client.ts:132-153 (helper)ComfyUIClient.uploadImage — sends image data as multipart/form-data to ComfyUI's /upload/image endpoint.
async uploadImage( data: Buffer | Uint8Array, filename: string, options: { overwrite?: boolean; subfolder?: string } = {}, ): Promise<UploadResult> { const form = new FormData(); const blob = new Blob([new Uint8Array(data)], { type: "image/png" }); form.append("image", blob, filename); form.append("overwrite", options.overwrite ? "true" : "false"); if (options.subfolder) form.append("subfolder", options.subfolder); const res = await fetch(`${this.baseUrl}/upload/image`, { method: "POST", body: form, }); if (!res.ok) { throw new Error( `ComfyUI image upload failed: ${res.status} ${await res.text()}`, ); } return (await res.json()) as UploadResult; } - src/comfyui/client.ts:155-168 (helper)ComfyUIClient.fetchAndUploadImage — fetches an image from a URL first, then calls uploadImage.
async fetchAndUploadImage( sourceUrl: string, filename?: string, ): Promise<UploadResult> { const res = await fetch(sourceUrl); if (!res.ok) { throw new Error( `Failed to fetch source image ${sourceUrl}: ${res.status}`, ); } const buffer = Buffer.from(await res.arrayBuffer()); const name = filename ?? deriveFilename(sourceUrl); return this.uploadImage(buffer, name); } - src/comfyui/client.ts:219-228 (helper)deriveFilename — extracts a filename from a URL or falls back to a timestamped name.
function deriveFilename(sourceUrl: string): string { try { const url = new URL(sourceUrl); const last = url.pathname.split("/").pop() ?? ""; if (last && /\.(png|jpg|jpeg|webp)$/i.test(last)) return last; } catch { // fall through } return `upload-${Date.now()}.png`; } - src/comfyui/types.ts:42-46 (helper)UploadResult interface: name, subfolder, type fields returned by the upload.
export interface UploadResult { name: string; subfolder: string; type: string; }