{"slug": "teaching-claude-code-to-paint-a-stateful-image-editing-skill-built-on-gemini-s", "title": "Teaching Claude Code to Paint: A Stateful Image-Editing Skill Built on Gemini's Interactions API and MCP", "summary": "A developer built a stateful image-editing skill for Claude Code by wrapping Google's gemini-3.1-flash-lite-image model in a FastMCP server. The tool, called nb2lite-skill-claude, uses Gemini's Interactions API to allow iterative edits without re-prompting the entire scene. The developer said, 'You type \"generate an image of a cyberpunk kitchen\" into Claude Code, and it just... does it. Then you say \"add a neon RAMEN sign\" and it edits the same image without re-prompting the whole scene.'", "body_md": "TL;DR:[nb2lite-skill-claude]wraps Google's`gemini-3.1-flash-lite-image`\n\nmodel in a tiny FastMCP server and packages it as a Claude Code skill. You type \"generate an image of a cyberpunk kitchen\" into Claude Code, and it just... does it. Then you say \"add a neon RAMEN sign\" and it editsthe same imagewithout re-prompting the whole scene. Oh, and the cover image of this article? Generated by the thing the article is about — dogfooding all the way down. More on that at the end.\n\nMost image-generation workflows are **stateless**. You send a prompt, you get pixels back, and the model immediately forgets everything. Want to tweak the result? You re-describe the *entire scene* and pray the character, lighting, and composition survive the round trip. (Narrator: they don't.)\n\nGoogle's Nano Banana 2 Lite — the friendly nickname for `gemini-3.1-flash-lite-image`\n\n— takes a different approach. It's a high-efficiency image model with sub-2-second generations, solid text rendering in 25+ languages, and — the headline feature — support for the **stateful Interactions API**, which lets you iterate on an image across multiple turns while the model keeps the visual context server-side.\n\nThis repo glues that capability into **Claude Code**, so your coding agent can generate and iteratively refine images as a natural part of a session. It ships as two things in one repo:\n\n`nb2lite-agent`\n\n, a single-file FastMCP app in `server.py`\n\n) exposing exactly four tools.`nb2lite-image`\n\n) that teaches Claude The Interactions API is Gemini's stateful endpoint. The core loop looks like this:\n\n`client.interactions.create(...)`\n\nwith a prompt and `store=True`\n\n.`interaction_id`\n\n`previous_interaction_id`\n\n, and the model edits the So instead of this (stateless suffering):\n\n\"A watercolor fox in a forest at dawn, mist, soft light, wearing a red scarf, three birch trees on the left,\n\nand now alsoholding a lantern\"\n\n...you write this:\n\n\"Add a lantern in its paw.\"\n\nThat's it. The stored context holds the rest.\n\nA few practical details the server handles for you:\n\n`1:1`\n\n, `16:9`\n\n, `9:16`\n\n, `4:3`\n\n, `3:4`\n\n) and `low`\n\n(default, fast drafts) or `high`\n\n(complex rendering, accurate text layout, character composition). The generic API spec also lists `minimal`\n\nand `medium`\n\n, but the live API rejects them for this model with an HTTP 400 — the server saves you from discovering that the hard way.The **Model Context Protocol** is an open standard for connecting AI assistants to tools and data. Before it, giving a model access to some service meant writing a bespoke integration for each assistant — N assistants × M services, everyone reinventing the same plumbing. MCP collapses that: a tool author writes one **MCP server** that exposes typed tools, and any MCP-capable client (Claude Code, Claude Desktop, and a growing list of others) can discover and call them with no per-client glue code.\n\nAn MCP server is usually a small local process that speaks JSON-RPC over stdio. The client launches it, asks \"what tools do you have?\", and from then on the model can call them like functions.\n\nThe `nb2lite-agent`\n\nserver exposes exactly four:\n\n| Tool | What it does |\n|---|---|\n`generate_image` |\nText → 1k image. Saves locally, returns the path + an interaction ID. |\n`edit_image` |\nStateful edit: takes the previous interaction ID + a description of only the change. |\n`edit_local_image` |\nUploads any local image file inline (base64) and applies an edit — your entry point for existing files. |\n`get_help` |\nReports live config: API key status, active model, output directory, full tool reference. |\n\nImages land on disk as `gen_<timestamp>_<uuid8>.jpg`\n\n(or `edit_`\n\n/`edit_local_`\n\nprefixed) — the UUID suffix keeps concurrent generations from clobbering each other. Errors come back as `🔴 ...`\n\ntext strings rather than protocol errors, so the agent can read and react to them.\n\nIf MCP is the *hands* (the tools Claude can physically call), a **skill** is the *muscle memory* — a markdown file (`SKILL.md`\n\n) plus bundled resources that load into Claude's context and teach it the workflow: which tool to reach for, in what order, with which constraints.\n\nFor `nb2lite-image`\n\n, the skill encodes things like:\n\n`get_help`\n\nfirst when diagnosing setup issues — if the API key is missing, nothing else will work.`thinking_level: low`\n\nfor drafts.The skill also bundles the MCP server itself (`mcp/server.py`\n\n), its requirements, an installer script, and a vendored copy of the Interactions API developer guide — so it's self-contained: install the skill, and you have everything needed to also stand up the server.\n\nYou need three things: **Python 3.10+**, **Claude Code**, and a **Gemini API key** (free from [Google AI Studio](https://aistudio.google.com/)). Pick *one* of the paths below.\n\nInside Claude Code, type:\n\n```\n/plugin marketplace add xbill9/nb2lite-skill-claude\n/plugin install nb2lite-image@nb2lite-skill-claude\n```\n\nThis installs the skill **and** auto-registers the MCP server. The plugin manifest carries no API key (as it should!) — the server reads `GEMINI_API_KEY`\n\nfrom your environment, so make sure it's exported before launching Claude Code.\n\n```\n# 1. Get the code\ngit clone https://github.com/xbill9/nb2lite-skill-claude.git\ncd nb2lite-skill-claude\n\n# 2. One-command setup: installs deps, registers the MCP server\n#    in .mcp.json, and prompts for your API key (stored in ~/gemini.key)\n./init.sh\n\n# 3. Restart Claude Code in this directory and approve the server\n#    when prompted. Verify with:\n/mcp        # should list nb2lite-agent\n```\n\nThat's genuinely it. `init.sh`\n\nis safe to rerun if anything looks off.\n\nFrom a clone of the repo:\n\n```\nmake init TARGET=/path/to/your/project ARGS='--output-dir ./images'\n```\n\nThis copies the skill into `<project>/.claude/skills/nb2lite-image/`\n\nand writes the `nb2lite-agent`\n\nentry into that project's `.mcp.json`\n\n. It reuses `~/gemini.key`\n\nif you've set one up. Restart Claude Code in the target project, approve the server, done.\n\nThe server is published as [ xbill9/nb2lite-agent](https://hub.docker.com/r/xbill9/nb2lite-agent):\n\n```\nclaude mcp add nb2lite-agent --env GEMINI_API_KEY=\"$(cat ~/gemini.key)\" -- \\\n  docker run --rm -i -e GEMINI_API_KEY -v \"$PWD:$PWD\" -w \"$PWD\" xbill9/nb2lite-agent\n```\n\nThe `-v \"$PWD:$PWD\" -w \"$PWD\"`\n\nmount matters: the server saves images to disk and reads local files for `edit_local_image`\n\n, so the container must see your project at the *same absolute path* as the host.\n\n`/mcp`\n\ndoesn't list the server → restart Claude Code in the project directory.`🔴 GEMINI_API_KEY is not set`\n\n→ run `source set_env.sh`\n\n(or export the key) and restart.`get_help`\n\n; it reports the live config.Once installed, you talk to it in plain English. A real flow looks like:\n\n**You:** *\"Generate a cozy cabin in a snowy forest at dusk, 16:9.\"*\n\nClaude calls:\n\n```\ngenerate_image(\n    prompt=\"A cozy log cabin in a snowy forest at dusk, warm light in the windows\",\n    aspect_ratio=\"16:9\",\n    thinking_level=\"low\",\n)\n# 🟢 Saved to: ./gen_1784759001_a1b2c3d4.jpg\n# Interaction ID: v1_ChdpRU5...\n```\n\n**You:** *\"Nice. Add smoke curling from the chimney.\"*\n\n```\nedit_image(\n    previous_interaction_id=\"v1_ChdpRU5...\",\n    edit_prompt=\"add gentle smoke curling from the chimney\",\n)\n# 🟢 Saved to: ./edit_1784759050_e5f6a7b8.jpg\n# Interaction ID: v1_Xk9mPq2...   ← a NEW id; the next edit chains this one\n```\n\n**You:** *\"Now make it night, with aurora in the sky.\"*\n\nSame tool, newest ID, and the cabin, trees, and chimney smoke all stay put — only the sky changes. No re-prompting, no continuity roulette.\n\nAnd for images that didn't come from the model at all:\n\n**You:** *\"Take ./whiteboard-sketch.png and render it as a clean 3D product mockup.\"*\n\n```\nedit_local_image(\n    image_path=\"./whiteboard-sketch.png\",\n    edit_prompt=\"render this hand-drawn sketch as a high-fidelity 3D product mockup\",\n    aspect_ratio=\"4:3\",\n)\n```\n\nIt returns an interaction ID too — so follow-up refinements switch to `edit_image`\n\nand go stateful from there.\n\nIf the term is new to you: **\"eating your own dog food\"** means using your own product for real work, not just demoing it. It's the difference between \"this should work\" and \"I ship with this every day.\" If a tool is good enough for your users, it should be good enough for you — and if it isn't, you'll be the first to feel the pain and fix it.\n\nThis repo dogfoods itself at every layer:\n\n`nb2lite-image`\n\nskill and `nb2lite-agent`\n\nserver are already wired up, so every development session doubles as an integration test.`make test`\n\n) drive the same four MCP tools an end user would, against the live API.\n\n```\ngenerate_image(\n    prompt=\"A wide tech blog cover illustration: a friendly robot artist \"\n           \"painting a glowing galaxy on an easel, while a chain of connected \"\n           \"frames behind it shows the same picture evolving step by step \"\n           \"(day sky, then sunset, then storm with lightning). Flat vector \"\n           \"style, deep indigo background, neon cyan and orange accents. \"\n           \"Title text 'NB2Lite + MCP', subtitle 'Stateful image editing \"\n           \"as a Claude Code skill'. Crisp, accurate lettering.\",\n    aspect_ratio=\"16:9\",\n    thinking_level=\"high\",\n)\n# 🟢 Image successfully saved!\n# • Saved to: gen_1784759177_cbab8b65.jpg\n# • Interaction ID: v1_ChdpRU5hb2o3SWMzV2pNY1AtUFgy...\n```\n\n(That exact output is committed to the repo as [ devto-cover.jpg](https://github.com/xbill9/nb2lite-skill-claude/blob/main/devto-cover.jpg), receipts and all.)\n\nWorth noticing:\n\n`thinking_level: \"high\"`\n\nbuys you on text-heavy layouts.`edit_image`\n\nwith that interaction ID and say \"make the orange accents magenta.\" That's the whole point.Dogfooding is the cheapest credibility there is: no cherry-picked gallery, no \"results may vary\" fine print — the tool's real output is literally the first thing you saw when you opened this article. If the skill had flubbed the lettering or mangled the layout, you'd be looking at the evidence right now. Instead, the article ships with its own proof baked into the header.\n\n*This is a third-party community project, not affiliated with or endorsed by Anthropic or Google. Bring your own Gemini API key — and remember generations are billable, so draft on low and save high for the money shot.*", "url": "https://wpnews.pro/news/teaching-claude-code-to-paint-a-stateful-image-editing-skill-built-on-gemini-s", "canonical_source": "https://dev.to/gde/teaching-claude-code-to-paint-a-stateful-image-editing-skill-built-on-geminis-interactions-api-17g", "published_at": "2026-07-22 22:44:23+00:00", "updated_at": "2026-07-22 22:58:36.898706+00:00", "lang": "en", "topics": ["artificial-intelligence", "generative-ai", "developer-tools", "ai-tools", "computer-vision"], "entities": ["Google", "Gemini", "Claude Code", "Anthropic", "FastMCP", "Model Context Protocol"], "alternates": {"html": "https://wpnews.pro/news/teaching-claude-code-to-paint-a-stateful-image-editing-skill-built-on-gemini-s", "markdown": "https://wpnews.pro/news/teaching-claude-code-to-paint-a-stateful-image-editing-skill-built-on-gemini-s.md", "text": "https://wpnews.pro/news/teaching-claude-code-to-paint-a-stateful-image-editing-skill-built-on-gemini-s.txt", "jsonld": "https://wpnews.pro/news/teaching-claude-code-to-paint-a-stateful-image-editing-skill-built-on-gemini-s.jsonld"}}