An image tool for the agent: make or edit pictures through a pluggable engine (default: the official OpenAI Images API). Room to grow into video.

An image tool for the agent (mcp__image__image). It can make a picture from a prompt, or edit and compose from up to eight reference images, and returns the path of the saved PNG so the agent can Read it and look at the result. The shape leaves room for a video tool beside it later.
The engine is bin/imagegen.py and it is pluggable.
OPENAI_API_KEY. Plain generation uses /v1/images/generations; with reference images it uses /v1/images/edits. This is billed to your OpenAI account per image.IMAGE_ENGINE=module:/absolute/path/to/engine.py, with one function generate(prompt, size, quality, model, refs) -> bytes. See engines/README.md and engines/example_engine.py.Note: the default engine was written against the documented API shape and has been exercised against a local stand-in server, but not against a live OpenAI account in this release. If the API rejects a parameter for your chosen model, set IMAGE_MODEL or use a custom engine.
Our own copy runs on a ChatGPT subscription through OAuth, an approach we took from Hermes Agent (https://github.com/NousResearch/hermes-agent). If you want to try that route, see how Hermes Agent does it. That code is not part of this mod.
~/.claude/mods/image.CLAUDE_CODE_PLUGIN_DIRS=/path/to/image (colon-separate several) in the environment that starts Claude Code, or pass --plugin-dir /path/to/image.Requires python3 on the path (standard library only).
Environment variables, set in the environment that starts Claude Code:
| Variable | Meaning | Default |
|---|---|---|
OPENAI_API_KEY | key for the default engine | none (required for it) |
IMAGE_ENGINE | openai, or module:/path/to/engine.py | openai |
IMAGE_MODEL | model name passed to the engine | gpt-image-1 |
IMAGE_OUT_DIR | where PNGs are saved | ~/generated-images |
IMAGE_API_BASE | base URL of an OpenAI-compatible API | https://api.openai.com/v1 |
Keep the key in your shell profile or a secrets manager, not in this folder.
claude plugin validate .
Mods (function hooks) are an early-access feature of Claude Code and may change between releases without notice. Claude Code writes the type declarations into .claude-plugin/types/ itself when it loads the mod, so they are not included here. Image generation costs money on most backends; the agent can call it freely, so consider telling it when to.
hooks/register.ts 51 lines1import type { Register } from 'claude-code'
2
3// image: the agent's image tool. The engine is bin/imagegen.py, which is pluggable
4// (default: the official OpenAI Images API with OPENAI_API_KEY; see README.md and engines/README.md).
5// Shaped so a `video` tool can join later beside `image`.
6const SIZES = ['1024x1024', '1536x1024', '1024x1536', 'auto']
7
8export const register: Register = on => {
9 on('session.start', async ($, e, next) => {
10 await $.tool.register({
11 name: 'image',
12 description: "Make a picture (or edit one, given reference images) through the configured image engine. Returns the saved PNG path; Read it to see the result. Can take up to a few minutes.",
13 inputSchema: {
14 type: 'object',
15 properties: {
16 prompt: { type: 'string', description: 'The full prompt: style, subject, scene, light, composition, and what to avoid.' },
17 size: { type: 'string', enum: SIZES, description: 'Default 1536x1024 (landscape). 1024x1536 portrait, 1024x1024 square.' },
18 quality: { type: 'string', enum: ['low', 'medium', 'high'], description: 'Default high.' },
19 refs: { type: 'array', items: { type: 'string' }, description: 'Optional reference images (local paths or https URLs, up to 8). With refs, the call edits/composes from them.' },
20 name: { type: 'string', description: 'Optional short slug for the file name.' },
21 },
22 required: ['prompt'],
23 },
24 })
25 return next(e)
26 })
27
28 on('tool.call', { tool: 'mcp__image__image' }, async ($, e) => {
29 const i = e as unknown as { prompt?: string; size?: string; quality?: string; refs?: string[]; name?: string }
30 const engine = `${$.plugin.root}/bin/imagegen.py`
31 const argv = ['python3', engine, '--prompt', String(i.prompt ?? '')]
32 if (i.size && SIZES.includes(i.size)) argv.push('--size', i.size)
33 if (i.quality) argv.push('--quality', String(i.quality))
34 if (i.name) argv.push('--name', String(i.name))
35 for (const r of (Array.isArray(i.refs) ? i.refs : []).slice(0, 8)) argv.push('--ref', String(r))
36 try {
37 const r = await $.process.run(argv, { timeoutMs: 330000 })
38 const line = r.stdout.trim().split('\n').pop() ?? ''
39 try {
40 const out = JSON.parse(line) as { ok: boolean; path?: string; error?: string; size?: string; mode?: string; model_requested?: string }
41 if (out.ok) return { result: `image saved: ${out.path} (${out.mode}, ${out.size}, model ${out.model_requested}). Read the file to see it.` }
42 return { result: `image failed: ${out.error ?? 'unknown error'}` }
43 } catch {
44 return { result: `image failed: exit ${r.exitCode}: ${(r.stderr || line).trim().slice(0, 300)}` }
45 }
46 } catch (err) {
47 return { result: `image failed: ${err instanceof Error ? err.message : String(err)}` }
48 }
49 })
50}
51