Nano Banana 2 Lite, Revisited: MCP 2.0, the New Interactions API, and Three Agent CLIs
This article provides a step by step update guide for a Python MCP server that drives Google Nano Banana 2 Lite (gemini-3.1-flash-lite-image) through the Gemini Interactions API. Two dependency lines moved underneath it since it was first published: the MCP Python SDK went to 2.x, and the Interactions API dropped the schema that google-genai 1.x speaks. The same server is then registered with Claude Code, Codex and Antigravity CLI, and validated end to end against the live API. https://github.com/xbill9/nb2lite Haven't You Done This One Before? What is old is new — again. The original article set up this server with Claude Code in July: Nano Banana 2 Lite with Claude Code The code in that article no longer runs on a fresh install. Nothing in the repository was broken; requirements.txt listed mcp and google-genai with no version bounds, and both resolved to a new major version. July (original article) September (this article) MCP SDK from mcp.server.fastmcp import FastMCP from mcp.server.mcpserver import MCPServer google-genai unpinned, 1.x google-genai>=2,=2 renamed FastMCP to MCPServer) +mcp = MCPServer("NB2Lite Agent") @mcp.tool() and mcp.run() stay as they are, and so does every tool body. The full walk-through of the 2.x changes, with the exposure greps, is in the companion article: FastMCP Is Now MCPServer: Migrating a Python MCP Server to the MCP SDK 2.x Two 2.x details showed up in this repository. list_tools() is async on MCPServer. The old test reached into a private attribute. The new one uses the public API: - tools = [t.name for t in mcp._tool_manager.list_tools()] + tools = [t.name for t in asyncio.run(mcp.list_tools())] The server version went blank. An unversioned 2.x server reports an empty string in the handshake, which shows up in the protocol test below. Pin Both Major Versions Both breaks came from unbounded requirements, so both lines now carry a floor and a ceiling: -google-genai -mcp +google-genai>=2,=2,/dev/null The responses are JSON; summarised: initialize OK: name='NB2Lite Agent' version='' proto 2025-06-18 tools/list OK: 5 tools -> generate_image, edit_image, edit_local_image, edit_local_image_with_style, get_help 🟢 Five tools, and version='' — the blank version from MCP 2.x. Validation with Claude Code The July article registered the server with a project .mcp.json. The repository is now also a Claude Code plugin marketplace, which bundles the server and the verify-live skill: claude plugin marketplace add xbill9/nb2lite claude plugin install nb2lite@nb2lite The API key goes in the plugin's configuration under /plugin. Left blank, the server falls back to GEMINI_API_KEY from the environment Claude Code started in. 🔎 Tip: a plugin env value wins even when it is empty. Mapping the plugin's key setting straight to GEMINI_API_KEY would blank a key the user had already exported. The plugin sets its own variable, NB2LITE_GEMINI_API_KEY, and the server checks that first, then GEMINI_API_KEY, then GOOGLE_API_KEY. Manual registration still works, and reads the key at launch so it never lands in a config file: claude mcp add --scope user nb2lite -- bash -c 'GEMINI_API_KEY=$(cat ~/gemini.key) exec python3 /path/to/nb2lite/server.py' After changing server.py or upgrading a dependency, reconnect the server from /mcp. Validation with Codex Same command, Codex syntax: codex mcp add nb2lite -- bash -c 'GEMINI_API_KEY=$(cat ~/gemini.key) exec python3 /path/to/nb2lite/server.py' Interactive Codex asks before each MCP tool call. Non-interactive codex exec has nobody to ask, so the call fails: mcp: nb2lite/get_help started mcp: nb2lite/get_help (failed) MCP tool call requires approval, but approval policy is never The fix is a per-server setting in ~/.codex/config.toml: [mcp_servers.nb2lite] command = "bash" args = ["-c", "GEMINI_API_KEY=$(cat ~/gemini.key) exec python3 /path/to/nb2lite/server.py"] default_tools_approval_mode = "approve" mcp: nb2lite/get_help (completed) ### 🌌 NB2Lite Agent (gemini-3.1-flash-lite-image) Help & Configuration Inside the repository, Codex also picked up AGENTS.md and the verify-live skill from .agents/skills/. ✅ Validation with Antigravity CLI agy mcp add nb2lite -- bash -c 'GEMINI_API_KEY=$(cat ~/gemini.key) exec python3 /path/to/nb2lite/server.py' agy mcp list Added MCP server "nb2lite" (stdio) NAME TYPE STATUS COMMAND/URL nb2lite stdio enabled bash -c GEMINI_API_KEY=$(cat ~/gemini.key) exec python3 /home/xbill/nb2lite/server.py Print mode called the tools with no extra approval setting. Attach the prompt to the flag and put other flags first — a bare -p followed by another flag takes that flag as the prompt: agy --print-timeout 3m -p="Call the nb2lite MCP server's get_help tool. Reply with only the first line of its output verbatim, then the names of the nb2lite tools available to you." ### 🌌 NB2Lite Agent (gemini-3.1-flash-lite-image) Help & Configuration - `generate_image` - `edit_image` - `edit_local_image` - `edit_local_image_with_style` - `get_help` 🔎 Tip: agy did not find the repository's skills. Codex read .agents/skills/; agy's CLI did not, even for a real directory rather than a symlink. It does read the global skills folder, and a symlink there works: mkdir -p ~/.gemini/config/skills ln -s /path/to/nb2lite/skills/verify-live ~/.gemini/config/skills/verify-live There is A Skill for That! Mocked tests pass while the API is broken, so the repository ships a skill that checks the path users actually hit. verify-live runs the unit tests, then chains all four image tools through the running MCP server at thinking_level="minimal", and opens every image it saved. The skill is written once and exposed to each client: through the plugin for Claude Code, .agents/skills/ for Codex, and the global link for agy. The run from Claude Code: Step Tool Result 1 generate_image — a red cube on a white table 🟢 red cube, white table 2 edit_image — make the cube blue 🟢 same composition, only the cube recoloured 3 edit_local_image — add a green sphere 🟢 sphere added beside the cube 4 generate_image — watercolor sunflowers 🟢 the style reference 5 edit_local_image_with_style — cube in the reference's style 🟢 the cube scene as a watercolor, no sunflowers Step 2 is the Interactions API test, since it proves the stored session came back. Step 5 is the new tool. The style reference must look nothing like the cube photo, or a transfer cannot be told apart from a copy. Enough, Already! Show me the Money! Claude Code was started for a hands-on session with the migrated server: generate_image(prompt="pixel-art ghost banana character with big friendly eyes, floating, dark indigo background, crisp 16-bit style", aspect_ratio="16:9", thinking_level="minimal") 🟢 Image successfully saved! • Saved to: /home/xbill/nb2lite/gen_1789322371_4fc516ed.jpg • Interaction ID: v1_ChdndVNtYXN6ZkRQcmRqTWNQbXV5QjJBZxIXZ3VTbWFzemZEUHJkak1jUG11eUIyQWc Not a fan of plain bananas? Continue the stored session with the interaction ID: edit_image(previous_interaction_id="v1_ChdndVNtYXN6ZkRQcmRqTWNQbXV5QjJBZxIXZ3VTbWFzemZEUHJkak1jUG11eUIyQWc", edit_prompt="make the ghost banana steampunk: brass gears, rivets and goggle eyes", thinking_level="minimal") 🟢 Image successfully saved! • Saved to: /home/xbill/nb2lite/edit_1789322402_e466080a.jpg • Interaction ID: v1_ChdndVNtYXN6ZkRQcmRqTWNQbXV5QjJBZxIXb2VTbWFxaVhEcUt3MU1rUGt1Zmh3UXM The pose, the glow, the stars and the constellation lines all carried over. Only the banana changed. Not a fan of steampunk? The new tool takes a style from a second image. First, a reference: generate_image(prompt="a traditional Japanese ukiyo-e woodblock print of a great wave, flat colors, bold outlines, visible paper grain", aspect_ratio="16:9", thinking_level="minimal") 🟢 Image successfully saved! • Saved to: /home/xbill/nb2lite/gen_1789322407_8dc09a3f.jpg Then the original banana, in that style: edit_local_image_with_style(image_path="gen_1789322371_4fc516ed.jpg", style_image_path="gen_1789322407_8dc09a3f.jpg", edit_prompt="keep the banana character and its big eyes", aspect_ratio="16:9", thinking_level="minimal") 🟢 Image successfully saved! • Saved to: /home/xbill/nb2lite/style_edit_1789322423_7efbadf5.jpg 🔎 About That Cover The cover of this article was generated by the server this article describes, from the same Claude Code session. One generate_image call at thinking_level="high", because the cover carries lettering: generate_image(prompt="A wide tech blog cover illustration, all important content kept inside a central horizontal band ... Large crisp title text in the center band: 'Nano Banana 2 Lite, Revisited'. Smaller subtitle beneath it: 'MCP 2.0 + the new Interactions API'. Accurate, typo-free lettering.", aspect_ratio="16:9", thinking_level="high") 🟢 Image successfully saved! • Saved to: /home/xbill/nb2lite/gen_1789323099_2380472d.jpg • Interaction ID: v1_ChdXdWVtYXNMTkw5aW5fUFVQOXUtRHFRURIXV3VlbWFzTE5MOWluX1BVUDl1LURxUVE The title and subtitle came out exact. The middle frame did not: it was labelled STEAMBOAT. That is the stateful edit loop's job, so the fix was one more call on the same interaction rather than a new prompt: edit_image(previous_interaction_id="v1_ChdXdWVtYXNMTkw5aW5fUFVQOXUtRHFRURIXV3VlbWFzTE5MOWluX1BVUDl1LURxUVE", edit_prompt="Change only the label under the middle frame from 'STEAMBOAT' to 'STEAMPUNK'. Keep everything else exactly the same.", thinking_level="high") 🟢 Image successfully saved! • Saved to: /home/xbill/nb2lite/edit_1789323124_176d08c8.jpg • Interaction ID: v1_ChdXdWVtYXNMTkw5aW5fUFVQOXUtRHFRURIXYy1lbWF1MzBDLXViX3VNUC1QdUh5UTg Only the label changed. The prompt kept everything important in a central band, so cropping the 16:9 output to the box dev.to displays removed nothing but background. No retouching. Cheat Sheet # pins # google-genai>=2,=2, MCPServer) make install && make lint && make test # stdio smoke test: hold stdin open { printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"p","version":"0"}}}' \ '{"jsonrpc":"2.0","method":"notifications/initialized"}' \ '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'; sleep 4; } | python3 server.py 2>/dev/null # register claude plugin marketplace add xbill9/nb2lite && claude plugin install nb2lite@nb2lite codex mcp add nb2lite -- bash -c 'GEMINI_API_KEY=$(cat ~/gemini.key) exec python3 /path/to/nb2lite/server.py' agy mcp add nb2lite -- bash -c 'GEMINI_API_KEY=$(cat ~/gemini.key) exec python3 /path/to/nb2lite/server.py' # codex exec: [mcp_servers.nb2lite] default_tools_approval_mode = "approve" # agy skills: ln -s /path/to/nb2lite/skills/verify-live ~/.gemini/config/skills/verify-live Summary The goal of this article was to bring the Nano Banana 2 Lite MCP server from July back to a working state on current dependencies, and to run it from more than one agent CLI. The key to the solution was reading the two error messages, which named both fixes, and then proving the live API path instead of trusting mocked tests. The update results were: ❌ google-genai 1.x now gets HTTP 400 from the Interactions API; the fix was google-genai>=2,
This is a summary aggregated from Dev.to. Read the complete article on the original site:
Read full article at Dev.to