{"slug": "nano-banana-2-lite-in-kiro-cli-3-mcp-2-0-the-new-interactions-api-and-headless", "title": "Nano Banana 2 Lite in Kiro CLI 3: MCP 2.0, the New Interactions API, and Headless Permissions", "summary": "A developer published an update guide for the nb2lite-kiro project, a Python MCP server that drives Google's Gemini 3.1 Flash-Lite Image model (nicknamed Nano Banana 2 Lite) through the Gemini Interactions API inside Kiro CLI 3. The update migrates the server to MCP Python SDK 2.x and google-genai 2.x after Google removed the legacy Interactions API schema on 2026-06-08, and adds a fifth tool, edit_local_image_with_style, plus headless permission rules and a live verification skill. The server code itself was unchanged, since it already read the interaction's output_image field.", "body_md": "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, running inside Kiro CLI 3. Two dependency lines moved underneath the server: the MCP Python SDK went to 2.x, and the Interactions API dropped the schema that google-genai 1.x speaks. The server is then registered with Kiro, given a permission rule, and validated end to end against the live API from a headless Kiro 3 session.\n\n[https://github.com/xbill9/nb2lite-kiro](https://github.com/xbill9/nb2lite-kiro)\n\nWhat is old is new — again.\n\nThe same update was written up for Claude Code, Codex and Antigravity CLI:\n\n[Nano Banana 2 Lite, Revisited: MCP 2.0, the New Interactions API, and Three Agent CLIs](https://dev.to/gde/nano-banana-2-lite-revisited-mcp-20-the-new-interactions-api-and-three-agent-clis-37g5)\n\nThis is the Kiro edition. `nb2lite-kiro` tracks [xbill9/nb2lite](https://github.com/xbill9/nb2lite), and `server.py`, `test_agent.py`, `requirements.txt` and the `Makefile` are byte-identical between the two. Everything that differs is how Kiro launches the server, how it is allowed to call it, and where it finds the skill.\n\n|  | Before | After | \n|---|---|---|\n| MCP SDK | `mcp.server.fastmcp.FastMCP` | `mcp.server.mcpserver.MCPServer` | \n| google-genai | unpinned, 1.x | `google-genai>=2,<3` | \n| Tools | 4 | 5 — adds `edit_local_image_with_style` | \n| Kiro server name | `nb2lite-agent` | `nb2lite` | \n| API key | written into `mcp.json` and`.env` | read from `~/gemini.key` at launch | \n| Live check | manual | `.kiro/skills/verify-live` | \n\n**Nano Banana 2 Lite** is the nickname for **Gemini 3.1 Flash-Lite Image**, Google's low-latency image generation and editing model:\n\n[Gemini 3.1 Flash-Lite Image (Nano Banana 2 Lite) | Google Cloud Documentation](https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/gemini/3-1-flash-lite-image)\n\nThe **Interactions API**. Every call is stored server-side with `store=True` and returns an interaction ID. Pass that ID back as `previous_interaction_id` and the model edits the image it already made, instead of redrawing a scene from a fresh prompt.\n\nThat keeps the MCP surface small. `generate_image` starts a session, `edit_image` continues one, and Kiro only has to carry an ID between turns.\n\nThe server's code did not change. The API moved away from the SDK it was installed with.\n\nThis is the call `server.py` makes, run with google-genai 1.x from a scratch install:\n\n```\nc = genai.Client(api_key=...)\nc.interactions.create(\n    model=\"gemini-3.1-flash-lite-image\",\n    input=\"a small red cube on a white table\",\n    response_format={\"type\": \"image\"},\n    generation_config={\"thinking_level\": \"minimal\"},\n    store=True,\n)\ngoogle-genai 1.75.0\nBadRequestError: Error code: 400 - {'error': {'message': 'The legacy Interactions API schema is no longer supported. Please upgrade your google-genai Python SDK to version >= 2.0.0 (e.g., run pip install -U google-genai) to use the Interactions API. For details and migration examples, see: https://ai.google.dev/gemini-api/docs/interactions-breaking-changes-may-2026', 'code': 'invalid_request'}}\n```\n\nThe message names the fix. Inside Kiro it is easy to miss: every tool catches the exception and returns it as a `🔴` string, so the agent reports \"Image generation failed\" with the version number buried in the text.\n\ngoogle-genai 2.x reads the new schema, where the model's output arrives as a list of **`steps`**. The SDK exposes the generated image as `interaction.output_image`, with `data` and `mime_type`.\n\n`server.py` already read `output_image`, so upgrading the SDK was the whole fix:\n\n```\nimage_output = getattr(interaction, \"output_image\", None)\n...\ndata = getattr(image_output, \"data\", None)\nif isinstance(data, str):\n    image_bytes = base64.b64decode(data)\nelse:\n    image_bytes = data\n```\n\nThe legacy schema was removed on 2026-06-08.\n\nThe unit tests mock `_get_client`, so the SDK never builds a real response and they pass against a broken API. One test now builds a real steps-schema `Interaction` with the SDK's own model and runs it through the response handler:\n\n```\ninteraction = Interaction.model_validate(\n    {\n        \"id\": \"int_steps\",\n        \"status\": \"completed\",\n        \"steps\": [\n            {\n                \"type\": \"model_output\",\n                \"content\": [\n                    {\"type\": \"image\", \"data\": \"aGVsbG8=\", \"mime_type\": \"image/png\"}\n                ],\n            }\n        ],\n    }\n)\nresult = _handle_response(interaction, \"steps\")\n```\n\nOn google-genai 1.x that import does not exist, so the test fails loudly instead of the API failing quietly. The rest of the gap is the live check later in this article.\n\nThe import and the constructor:\n\n``` python\n-from mcp.server.fastmcp import FastMCP\n+from mcp.server.mcpserver import MCPServer\n\n-# Initialize FastMCP Server\n-mcp = FastMCP(\"NB2Lite Agent\")\n+# Initialize MCP Server (mcp>=2 renamed FastMCP to MCPServer)\n+mcp = MCPServer(\"NB2Lite Agent\")\n```\n\n`@mcp.tool()`, `mcp.run()` and every tool body stay as they are. The full walk-through of the 2.x changes is in the companion article:\n\n[FastMCP Is Now MCPServer: Migrating a Python MCP Server to the MCP SDK 2.x](https://dev.to/gde/fastmcp-is-now-mcpserver-migrating-a-python-mcp-server-to-the-mcp-sdk-2x-2nhj)\n\n**`list_tools()` is async on `MCPServer`.** The old test reached into a private attribute:\n\n```\n-        tools = [t.name for t in mcp._tool_manager.list_tools()]\n+        tools = [t.name for t in asyncio.run(mcp.list_tools())]\n```\n\nBoth requirements now carry a floor and a ceiling, so the next major version arrives on purpose:\n\n```\n-google-genai\n-mcp\n+google-genai>=2,<3\n+mcp>=2,<3\ncd ~\ngit clone https://github.com/xbill9/nb2lite-kiro\ncd nb2lite-kiro\nmake install\nsource set_env.sh\n```\n\n`set_env.sh` reads the key from `~/gemini.key`, or prompts for it and saves it there with mode `600`. It then rewrites the `nb2lite` entry in `.kiro/settings/mcp.json` with this checkout's path.\n\n```\npython3 -m pip show mcp google-genai | grep -E \"^(Name|Version)\"\nkiro-cli --version\nName: google-genai\nVersion: 2.22.0\nName: mcp\nVersion: 2.2.0\nkiro-cli 2.21.4\nmake lint\nruff check .\nAll checks passed!\nruff format --check .\n6 files already formatted\nmypy .\nSuccess: no issues found in 2 source files\n```\n\n`mypy` is not in `requirements.txt`. On this machine `make lint` first failed with `make: mypy: No such file or directory`, and `python3 -m pip install mypy` fixed it.\n\n```\nmake test\n----------------------------------------------------------------------\nRan 12 tests in 0.412s\n\nOK\n```\n\nKiro speaks JSON-RPC over stdio, so test that too. Hold stdin open with `sleep`, or the server sees end-of-input before it answers:\n\n```\n{ printf '%s\\n' \\\n  '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2025-06-18\",\"capabilities\":{},\"clientInfo\":{\"name\":\"probe\",\"version\":\"0\"}}}' \\\n  '{\"jsonrpc\":\"2.0\",\"method\":\"notifications/initialized\"}' \\\n  '{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/list\",\"params\":{}}'; sleep 4; } \\\n  | python3 server.py 2>/dev/null\n```\n\nSummarised:\n\n```\ninitialize OK: name='NB2Lite Agent' version='' proto 2025-06-18\ntools/list OK: 5 tools -> generate_image, edit_image, edit_local_image, edit_local_image_with_style, get_help\n```\n\n🟢 Five tools. The blank `version` is what an unversioned MCP 2.x server reports.\n\nThe workspace config registers the server as `nb2lite`:\n\n```\n{\n  \"mcpServers\": {\n    \"nb2lite\": {\n      \"command\": \"bash\",\n      \"args\": [\n        \"-c\",\n        \"GEMINI_API_KEY=$(cat ~/gemini.key) exec python3 /home/xbill/nb2lite-kiro/server.py\"\n      ],\n      \"disabled\": false\n    }\n  }\n}\n```\n\n**🔎 Tip: the old setup put the key in the config.** The previous `init.sh` injected `GEMINI_API_KEY` into the server's `env` block in `mcp.json` and wrote a `.env` file beside it. The `bash -c` launch reads `~/gemini.key` each time Kiro starts the server, so the key never lands in a file inside the repository.\n\n```\nkiro-cli mcp list\nkiro-cli mcp status --name nb2lite\n🤖 default:\n  kiro_default\n    • aws-mcp      uvx\n    • nb2lite      bash\n\nScope   : 🤖 default\nAgent   : kiro_default\nCommand : bash\nTimeout : 120000 ms\nDisabled: false\nEnv Vars: (none)\n```\n\n`Env Vars: (none)` is the point: nothing secret in the registration.\n\nOn kiro-cli 2.21.4, the next generation agent is a flag:\n\n```\nkiro-cli chat --v3\n```\n\nIt starts the Kiro Agent Server, which logs its own version:\n\n```\n[INFO] kas.server.starting {\"product\":\"KAS (Kiro Agent Server)\",\"version\":\"0.63.3\"}\n```\n\nThe `mcp.json` above works unchanged under v3, and skills in `.kiro/skills/<name>/SKILL.md` are picked up the same way.\n\nInteractive Kiro asks before each MCP tool call. A non-interactive session has nobody to ask:\n\n```\nkiro-cli chat --v3 --no-interactive \"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.\"\n[tool] @nb2lite/get_help\n[denied] tool permission approval is not supported in non-interactive mode. Use --trust-all-tools to auto-approve.\n[tool] status: Failed\n```\n\nThe workspace `.kiro/settings/cli.json` in this repository sets `\"trustedTools\": [\"*\"]`. The v3 session denied the call anyway.\n\nThe error names one fix, and it works:\n\n```\nkiro-cli chat --v3 --no-interactive --trust-all-tools \"Call the nb2lite MCP server's get_help tool. ...\"\n[tool] @nb2lite/get_help\n[tool] status: Completed\nFirst line: `### 🌌 NB2Lite Agent (gemini-3.1-flash-lite-image) Help & Configuration`\n\nAvailable nb2lite tools:\n- `generate_image`\n- `edit_image`\n- `edit_local_image`\n- `edit_local_image_with_style`\n- `get_help`\n```\n\n`--trust-all-tools` trusts every tool, including shell. The narrower fix is a rule.\n\nKiro 3 reads permission rules from `permissions.yaml`: globally in `~/.kiro/settings/`, or per workspace under `~/.kiro/workspace-roots/<hash>/`. Both live outside the repository, so a checkout cannot grant itself permissions.\n\n```\nrules:\n  - capability: mcp\n    match: [\"nb2lite/*\"]\n    effect: allow\n```\n\nWith that file in `~/.kiro/settings/` and no trust flag:\n\n```\nkiro-cli chat --v3 --no-interactive \"Call the nb2lite MCP server's get_help tool. Reply with only the first line of its output verbatim.\"\n[tool] @nb2lite/get_help\n[tool] status: Completed\n### 🌌 NB2Lite Agent (gemini-3.1-flash-lite-image) Help & Configuration\n```\n\n✅ MCP tools are addressed as `<server>/<tool>`. A rule accepts only `capability`, `effect`, `match` and `exclude`; the parser in the shipped agent server rejects any other key as an unknown field.\n\nMocked tests pass while the API is broken, so the repository ships `.kiro/skills/verify-live/SKILL.md`. It runs the unit tests and lint, then chains all four image tools through the running MCP server at `thinking_level=\"minimal\"`, and opens every image it saved.\n\nRun headless from Kiro 3:\n\n```\nkiro-cli chat --v3 --no-interactive --trust-all-tools \"Run the verify-live skill exactly as written. For every nb2lite tool call, print the call and the tool's full result text verbatim, then your inspection of the image. Finish with a pass/fail table per step.\"\n```\n\n| Step | Tool | Result | \n|---|---|---|\n| 1 | `generate_image` — a red cube on a white table | 🟢 red cube, white table | \n| 2 | `edit_image` — make the cube blue | 🟢 same composition, only the cube recoloured | \n| 3 | `edit_local_image` — add a green sphere | 🟢 sphere added beside the cube | \n| 4 | `generate_image` — watercolor sunflowers | 🟢 the style reference | \n| 5 | `edit_local_image_with_style` — cube in the reference's style | 🟢 the cube scene as a watercolor, no sunflowers | \n\nKiro followed the skill's ordering, calling steps 1 and 4 together and then 2, 3 and 5 together, and deleted the images once it had inspected them. Step 2 is the Interactions API test, since it proves the stored session came back.\n\nThe same headless Kiro 3 session style, with four calls chained by the agent:\n\n```\ngenerate_image(prompt=\"a friendly pixel-art ghost banana with big eyes typing on a tiny glowing laptop, dark indigo background, crisp 16-bit style\", thinking_level=\"minimal\", aspect_ratio=\"16:9\")\n\n🟢 Image successfully saved!\n• Saved to: /home/xbill/nb2lite-kiro/gen_1789484185_cb29a061.jpg\n• Interaction ID: v1_ChdtRnlwYXBPRUtfbXUxTWtQNFB1UjRBVRIXbUZ5cGFwT0VLX211MU1rUDRQdVI0QVU\n```\n\nContinue the stored session with the interaction ID:\n\n```\nedit_image(previous_interaction_id=\"v1_ChdtRnlwYXBPRUtfbXUxTWtQNFB1UjRBVRIXbUZ5cGFwT0VLX211MU1rUDRQdVI0QVU\", edit_prompt=\"give the banana a small wizard hat and make the laptop screen show green terminal text\", thinking_level=\"minimal\")\n\n🟢 Image successfully saved!\n• Saved to: /home/xbill/nb2lite-kiro/edit_1789484197_dd06d5b0.jpg\n• Interaction ID: v1_ChdtRnlwYXBPRUtfbXUxTWtQNFB1UjRBVRIXcEZ5cGFxNmREOUNhOU1vUHR1REpvQUk\n```\n\nThe window, the shelves, the lantern and the pose all carried over. Only the hat and the screen changed.\n\nThe new tool takes a style from a second image. First, a reference:\n\n```\ngenerate_image(prompt=\"a Bauhaus poster, flat geometric shapes, primary colors, heavy black lines, off-white paper\", thinking_level=\"minimal\", aspect_ratio=\"16:9\")\n\n🟢 Image successfully saved!\n• Saved to: /home/xbill/nb2lite-kiro/gen_1789484204_75f1e1e0.jpg\n```\n\nThe model added poster lettering on its own — an exhibition, a venue and dates. All of it is invented.\n\nThen the original banana, in that style:\n\n```\nedit_local_image_with_style(image_path=\"/home/xbill/nb2lite-kiro/gen_1789484185_cb29a061.jpg\", style_image_path=\"/home/xbill/nb2lite-kiro/gen_1789484204_75f1e1e0.jpg\", edit_prompt=\"keep the banana, its eyes and the laptop\", thinking_level=\"minimal\", aspect_ratio=\"16:9\")\n\n🟢 Image successfully saved!\n• Saved to: /home/xbill/nb2lite-kiro/style_edit_1789484214_51da1f65.jpg\n```\n\nThe banana, the laptop and the room props came through. The poster's shapes and paper did too; its lettering did not.\n\nThe cover of this article was generated by the server this article describes, headless from Kiro 3. One `generate_image` call at `thinking_level=\"high\"`, because the cover carries lettering:\n\n```\ngenerate_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 in Kiro CLI 3'. Smaller subtitle beneath it: 'MCP 2.0 + the new Interactions API'. Accurate, typo-free lettering.\", aspect_ratio=\"16:9\", thinking_level=\"high\")\n\n🟢 Image successfully saved!\n• Saved to: /home/xbill/nb2lite-kiro/gen_1789484250_dd316079.jpg\n• Interaction ID: v1_ChcyVnlwYXBpUEVhalZqckVQdTRLeGlBTRIXMlZ5cGFwaVBFYWpWanJFUHU0S3hpQU0\n```\n\nThe subtitle came out exact. The title lost a word — \"Nano Banana 2 Lite Kiro CLI 3\" — and a line on the tiny terminal screen read **Kire CLI**. That is the stateful edit loop's job, so the fix was an `edit_image` on the same interaction:\n\n```\nedit_image(previous_interaction_id=\"v1_ChcyVnlwYXBpUEVhalZqckVQdTRLeGlBTRIXMlZ5cGFwaVBFYWpWanJFUHU0S3hpQU0\", edit_prompt=\"Fix only the lettering. The title must read exactly 'Nano Banana 2 Lite in Kiro CLI 3', and any on-screen text reading 'Kire' must read 'Kiro'. Keep everything else exactly the same.\", thinking_level=\"high\")\n```\n\nThat put \"in\" back and dropped \"Lite\". A second edit, naming each line of the title separately, got the title exact:\n\n```\nedit_image(previous_interaction_id=\"v1_ChcyVnlwYXBpUEVhalZqckVQdTRLeGlBTRIXQ0YycGF1bUxNdGFZak1jUHlaQ3prUW8\", edit_prompt=\"Fix only two pieces of lettering. The two-line title must read 'Nano Banana 2 Lite' on the first line and 'in Kiro CLI 3' on the second line. On the computer screen, the bottom line 'Kire CLI' must read 'Kiro CLI'. Keep everything else exactly the same.\", thinking_level=\"high\")\n\n🟢 Image successfully saved!\n• Saved to: /home/xbill/nb2lite-kiro/edit_1789484421_431dff69.jpg\n```\n\nThe characters, frames and composition held through both edits. The screen line did not: **Kire CLI** is still there, in the bottom line of the terminal. On this cover, the headline took two edits and the few-pixel screen text never came right. No retouching, beyond cropping the 16:9 output to the box dev.to displays.\n\n```\n# pins\n#   google-genai>=2,<3   (1.x: 400 \"legacy Interactions API schema is no longer supported\")\n#   mcp>=2,<3            (FastMCP -> MCPServer)\nmake install && make lint && make test     # lint needs mypy installed\nsource set_env.sh                          # key from ~/gemini.key, path into .kiro/settings/mcp.json\n\n# check the registration\nkiro-cli mcp list && kiro-cli mcp status --name nb2lite\n\n# Kiro 3\nkiro-cli chat --v3\n\n# headless: either trust everything...\nkiro-cli chat --v3 --no-interactive --trust-all-tools \"Run the verify-live skill\"\n\n# ...or allow only nb2lite, in ~/.kiro/settings/permissions.yaml\n# rules:\n#   - capability: mcp\n#     match: [\"nb2lite/*\"]\n#     effect: allow\n```\n\nThe goal of this article was to bring the Kiro edition of the Nano Banana 2 Lite MCP server back to a working state on current dependencies, and to run it from Kiro CLI 3. The key to the solution was reading the error messages, which named every fix, and then proving the live API path from a headless Kiro session instead of trusting mocked tests. The update results were:\n\n`google-genai>=2,<3`, with no change to `server.py`\n`mcp.json`; the server reads `~/gemini.key` at launch`--no-interactive` mode, even with `trustedTools` set in the workspace `cli.json`\n`--trust-all-tools` or a `permissions.yaml` rule for `nb2lite/*` both unblock it, and `verify-live` passed all five steps\nScope: one Debian 13 workstation, Python 3.14.7, mcp 2.2.0 and google-genai 2.22.0, with google-genai 1.75.0 as the failing reference from the upstream repository's run on the identical `server.py`. kiro-cli 2.21.4 with `--v3` (KAS 0.63.3), every Kiro run headless with `--no-interactive`; the interactive approval prompt was not exercised. Every demo image call ran once at `thinking_level=\"minimal\"` against `gemini-3.1-flash-lite-image`; nothing here measures latency or cost.\n\nThe strategy for using MCP with Nano Banana 2 Lite in Kiro CLI 3 was validated with an incremental step by step approach.\n\n*mcp 2.2.0, google-genai 2.22.0, Python 3.14.7, ruff 0.16.7, mypy 2.3.1, kiro-cli 2.21.4 (KAS 0.63.3), `gemini-3.1-flash-lite-image`.*", "url": "https://wpnews.pro/news/nano-banana-2-lite-in-kiro-cli-3-mcp-2-0-the-new-interactions-api-and-headless", "canonical_source": "https://dev.to/aws-builders/nano-banana-2-lite-in-kiro-cli-3-mcp-20-the-new-interactions-api-and-headless-permissions-hhb", "published_at": "2026-09-15 16:32:58+00:00", "updated_at": "2026-09-15 16:50:40.643907+00:00", "lang": "en", "topics": ["ai-tools", "developer-tools", "ai-products", "generative-ai", "mlops"], "entities": ["Google", "Gemini 3.1 Flash-Lite Image", "Nano Banana 2 Lite", "Kiro CLI 3", "Gemini Interactions API", "google-genai", "MCP Python SDK", "nb2lite-kiro"], "alternates": {"html": "https://wpnews.pro/news/nano-banana-2-lite-in-kiro-cli-3-mcp-2-0-the-new-interactions-api-and-headless", "markdown": "https://wpnews.pro/news/nano-banana-2-lite-in-kiro-cli-3-mcp-2-0-the-new-interactions-api-and-headless.md", "text": "https://wpnews.pro/news/nano-banana-2-lite-in-kiro-cli-3-mcp-2-0-the-new-interactions-api-and-headless.txt", "jsonld": "https://wpnews.pro/news/nano-banana-2-lite-in-kiro-cli-3-mcp-2-0-the-new-interactions-api-and-headless.jsonld"}}