# Nano Banana 2 Lite in Kiro CLI 3: MCP 2.0, the New Interactions API, and Headless Permissions

> 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: 2026-09-15 16:32:58+00:00

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.

[https://github.com/xbill9/nb2lite-kiro](https://github.com/xbill9/nb2lite-kiro)

What is old is new — again.

The same update was written up for Claude Code, Codex and Antigravity CLI:

[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)

This 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.

|  | Before | After | 
|---|---|---|
| MCP SDK | `mcp.server.fastmcp.FastMCP` | `mcp.server.mcpserver.MCPServer` | 
| google-genai | unpinned, 1.x | `google-genai>=2,<3` | 
| Tools | 4 | 5 — adds `edit_local_image_with_style` | 
| Kiro server name | `nb2lite-agent` | `nb2lite` | 
| API key | written into `mcp.json` and`.env` | read from `~/gemini.key` at launch | 
| Live check | manual | `.kiro/skills/verify-live` | 

**Nano Banana 2 Lite** is the nickname for **Gemini 3.1 Flash-Lite Image**, Google's low-latency image generation and editing model:

[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)

The **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.

That keeps the MCP surface small. `generate_image` starts a session, `edit_image` continues one, and Kiro only has to carry an ID between turns.

The server's code did not change. The API moved away from the SDK it was installed with.

This is the call `server.py` makes, run with google-genai 1.x from a scratch install:

```
c = genai.Client(api_key=...)
c.interactions.create(
    model="gemini-3.1-flash-lite-image",
    input="a small red cube on a white table",
    response_format={"type": "image"},
    generation_config={"thinking_level": "minimal"},
    store=True,
)
google-genai 1.75.0
BadRequestError: 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'}}
```

The 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.

google-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`.

`server.py` already read `output_image`, so upgrading the SDK was the whole fix:

```
image_output = getattr(interaction, "output_image", None)
...
data = getattr(image_output, "data", None)
if isinstance(data, str):
    image_bytes = base64.b64decode(data)
else:
    image_bytes = data
```

The legacy schema was removed on 2026-06-08.

The 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:

```
interaction = Interaction.model_validate(
    {
        "id": "int_steps",
        "status": "completed",
        "steps": [
            {
                "type": "model_output",
                "content": [
                    {"type": "image", "data": "aGVsbG8=", "mime_type": "image/png"}
                ],
            }
        ],
    }
)
result = _handle_response(interaction, "steps")
```

On 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.

The import and the constructor:

``` python
-from mcp.server.fastmcp import FastMCP
+from mcp.server.mcpserver import MCPServer

-# Initialize FastMCP Server
-mcp = FastMCP("NB2Lite Agent")
+# Initialize MCP Server (mcp>=2 renamed FastMCP to MCPServer)
+mcp = MCPServer("NB2Lite Agent")
```

`@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:

[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)

**`list_tools()` is async on `MCPServer`.** The old test reached into a private attribute:

```
-        tools = [t.name for t in mcp._tool_manager.list_tools()]
+        tools = [t.name for t in asyncio.run(mcp.list_tools())]
```

Both requirements now carry a floor and a ceiling, so the next major version arrives on purpose:

```
-google-genai
-mcp
+google-genai>=2,<3
+mcp>=2,<3
cd ~
git clone https://github.com/xbill9/nb2lite-kiro
cd nb2lite-kiro
make install
source set_env.sh
```

`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.

```
python3 -m pip show mcp google-genai | grep -E "^(Name|Version)"
kiro-cli --version
Name: google-genai
Version: 2.22.0
Name: mcp
Version: 2.2.0
kiro-cli 2.21.4
make lint
ruff check .
All checks passed!
ruff format --check .
6 files already formatted
mypy .
Success: no issues found in 2 source files
```

`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.

```
make test
----------------------------------------------------------------------
Ran 12 tests in 0.412s

OK
```

Kiro speaks JSON-RPC over stdio, so test that too. Hold stdin open with `sleep`, or the server sees end-of-input before it answers:

```
{ printf '%s\n' \
  '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"probe","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
```

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. The blank `version` is what an unversioned MCP 2.x server reports.

The workspace config registers the server as `nb2lite`:

```
{
  "mcpServers": {
    "nb2lite": {
      "command": "bash",
      "args": [
        "-c",
        "GEMINI_API_KEY=$(cat ~/gemini.key) exec python3 /home/xbill/nb2lite-kiro/server.py"
      ],
      "disabled": false
    }
  }
}
```

**🔎 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.

```
kiro-cli mcp list
kiro-cli mcp status --name nb2lite
🤖 default:
  kiro_default
    • aws-mcp      uvx
    • nb2lite      bash

Scope   : 🤖 default
Agent   : kiro_default
Command : bash
Timeout : 120000 ms
Disabled: false
Env Vars: (none)
```

`Env Vars: (none)` is the point: nothing secret in the registration.

On kiro-cli 2.21.4, the next generation agent is a flag:

```
kiro-cli chat --v3
```

It starts the Kiro Agent Server, which logs its own version:

```
[INFO] kas.server.starting {"product":"KAS (Kiro Agent Server)","version":"0.63.3"}
```

The `mcp.json` above works unchanged under v3, and skills in `.kiro/skills/<name>/SKILL.md` are picked up the same way.

Interactive Kiro asks before each MCP tool call. A non-interactive session has nobody to ask:

```
kiro-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."
[tool] @nb2lite/get_help
[denied] tool permission approval is not supported in non-interactive mode. Use --trust-all-tools to auto-approve.
[tool] status: Failed
```

The workspace `.kiro/settings/cli.json` in this repository sets `"trustedTools": ["*"]`. The v3 session denied the call anyway.

The error names one fix, and it works:

```
kiro-cli chat --v3 --no-interactive --trust-all-tools "Call the nb2lite MCP server's get_help tool. ..."
[tool] @nb2lite/get_help
[tool] status: Completed
First line: `### 🌌 NB2Lite Agent (gemini-3.1-flash-lite-image) Help & Configuration`

Available nb2lite tools:
- `generate_image`
- `edit_image`
- `edit_local_image`
- `edit_local_image_with_style`
- `get_help`
```

`--trust-all-tools` trusts every tool, including shell. The narrower fix is a rule.

Kiro 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.

```
rules:
  - capability: mcp
    match: ["nb2lite/*"]
    effect: allow
```

With that file in `~/.kiro/settings/` and no trust flag:

```
kiro-cli chat --v3 --no-interactive "Call the nb2lite MCP server's get_help tool. Reply with only the first line of its output verbatim."
[tool] @nb2lite/get_help
[tool] status: Completed
### 🌌 NB2Lite Agent (gemini-3.1-flash-lite-image) Help & Configuration
```

✅ 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.

Mocked 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.

Run headless from Kiro 3:

```
kiro-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."
```

| 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 | 

Kiro 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.

The same headless Kiro 3 session style, with four calls chained by the agent:

```
generate_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")

🟢 Image successfully saved!
• Saved to: /home/xbill/nb2lite-kiro/gen_1789484185_cb29a061.jpg
• Interaction ID: v1_ChdtRnlwYXBPRUtfbXUxTWtQNFB1UjRBVRIXbUZ5cGFwT0VLX211MU1rUDRQdVI0QVU
```

Continue the stored session with the interaction ID:

```
edit_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")

🟢 Image successfully saved!
• Saved to: /home/xbill/nb2lite-kiro/edit_1789484197_dd06d5b0.jpg
• Interaction ID: v1_ChdtRnlwYXBPRUtfbXUxTWtQNFB1UjRBVRIXcEZ5cGFxNmREOUNhOU1vUHR1REpvQUk
```

The window, the shelves, the lantern and the pose all carried over. Only the hat and the screen changed.

The new tool takes a style from a second image. First, a reference:

```
generate_image(prompt="a Bauhaus poster, flat geometric shapes, primary colors, heavy black lines, off-white paper", thinking_level="minimal", aspect_ratio="16:9")

🟢 Image successfully saved!
• Saved to: /home/xbill/nb2lite-kiro/gen_1789484204_75f1e1e0.jpg
```

The model added poster lettering on its own — an exhibition, a venue and dates. All of it is invented.

Then the original banana, in that style:

```
edit_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")

🟢 Image successfully saved!
• Saved to: /home/xbill/nb2lite-kiro/style_edit_1789484214_51da1f65.jpg
```

The banana, the laptop and the room props came through. The poster's shapes and paper did too; its lettering did not.

The 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:

```
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 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")

🟢 Image successfully saved!
• Saved to: /home/xbill/nb2lite-kiro/gen_1789484250_dd316079.jpg
• Interaction ID: v1_ChcyVnlwYXBpUEVhalZqckVQdTRLeGlBTRIXMlZ5cGFwaVBFYWpWanJFUHU0S3hpQU0
```

The 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:

```
edit_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")
```

That put "in" back and dropped "Lite". A second edit, naming each line of the title separately, got the title exact:

```
edit_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")

🟢 Image successfully saved!
• Saved to: /home/xbill/nb2lite-kiro/edit_1789484421_431dff69.jpg
```

The 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.

```
# pins
#   google-genai>=2,<3   (1.x: 400 "legacy Interactions API schema is no longer supported")
#   mcp>=2,<3            (FastMCP -> MCPServer)
make install && make lint && make test     # lint needs mypy installed
source set_env.sh                          # key from ~/gemini.key, path into .kiro/settings/mcp.json

# check the registration
kiro-cli mcp list && kiro-cli mcp status --name nb2lite

# Kiro 3
kiro-cli chat --v3

# headless: either trust everything...
kiro-cli chat --v3 --no-interactive --trust-all-tools "Run the verify-live skill"

# ...or allow only nb2lite, in ~/.kiro/settings/permissions.yaml
# rules:
#   - capability: mcp
#     match: ["nb2lite/*"]
#     effect: allow
```

The 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:

`google-genai>=2,<3`, with no change to `server.py`
`mcp.json`; the server reads `~/gemini.key` at launch`--no-interactive` mode, even with `trustedTools` set in the workspace `cli.json`
`--trust-all-tools` or a `permissions.yaml` rule for `nb2lite/*` both unblock it, and `verify-live` passed all five steps
Scope: 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.

The strategy for using MCP with Nano Banana 2 Lite in Kiro CLI 3 was validated with an incremental step by step approach.

*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`.*
