# Smash Story: The Demo Script That Out-Debugged My Test Suite

> Source: <https://dev.to/gde/smash-story-the-demo-script-that-out-debugged-my-test-suite-430k>
> Published: 2026-07-15 23:58:42+00:00

*This is a Smash Stories submission for the DEV Summer Bug Smash: a debugging story about the gap between "all tests pass" and "it actually works" — and the unlikely hero that closed it.*

The project is a small [MCP (Model Context Protocol) server](https://hub.docker.com/r/xbill9/nb2lite-mcp) that wraps Google's `gemini-3.1-flash-lite-image`

model. It exposes image generation and *stateful* image editing as four tools that any MCP-speaking agent can call — Claude Code, a Google ADK agent, and a Rust CLI all consume the same ~300-line Python server. (Full architecture write-up [here](https://dev.to/xbill/build-one-ai-tool-server-call-it-from-three-different-agents-mcp-explained-22l2).)

By every signal a developer normally trusts, it was healthy:

Then I wrote a demo script. It found a production bug in under a minute of runtime.

`demo.sh`

walks the stack live: discover the tools, generate an image, then do a stateful edit. To keep the demo cheap, step 2 requested the lowest quality tier the server documents:

```
cargo run --quiet -- generate "a tiny robot chef cooking ramen" 16:9 minimal
```

First run:

```
🔴 Image generation failed: Error code: 400 - {'error': {'message':
"'minimal' is not a supported thinking level for this model.
Allowed values are: low, high.", 'code': 'invalid_request'}}
```

Wait. The server's own validation had *approved* `minimal`

before sending it. Here's that validation:

```
# server.py — as shipped
SUPPORTED_THINKING_LEVELS = {"minimal", "low", "medium", "high"}

@mcp.tool()
def generate_image(
    prompt: str, aspect_ratio: str = "1:1", thinking_level: str = "medium"
) -> str:
    ...
```

Four allowed values. The live API accepts **two**: `low`

and `high`

. And look at the default — `medium`

. That's the real smash-worthy find:

Every live call that didn't explicitly overrideThe validation layer wasn't validating the API's contract — it was validating a stale memory of it.`thinking_level`

was a guaranteed HTTP 400.

The suite mocks the Gemini client, as unit tests should:

``` python
@patch("server._get_client")
def test_generate_image_success(self, mock_get_client):
    mock_client.interactions.create.return_value = mock_interaction
    result = generate_image(prompt="test", thinking_level="medium")
    self.assertIn("🟢 Image successfully saved!", result)
```

The mock returns success for *any* input — including inputs the real API rejects. The tests correctly proved "the server forwards `medium`

faithfully." Faithfully forwarding an invalid value is still a bug; it's just invisible from inside the mock boundary.

Two conditions had to align for this to ship:

`SUPPORTED_THINKING_LEVELS`

was a cached copy of a fact only the API owns. Cached copies drift.`high`

for quality — so the broken default and the two phantom values were never exercised. `f(x)`

being called a hundred times tells you nothing about `f()`

.Two lines of production code, plus the part that actually takes discipline — locking the discovery in so it can't regress:

```
-SUPPORTED_THINKING_LEVELS = {"minimal", "low", "medium", "high"}
+SUPPORTED_THINKING_LEVELS = {"low", "high"}

-    prompt: str, aspect_ratio: str = "1:1", thinking_level: str = "medium"
+    prompt: str, aspect_ratio: str = "1:1", thinking_level: str = "low"
# New regression test: the live API only accepts low/high for this
# model; medium must now be rejected locally with a readable error.
result = generate_image(prompt="test", thinking_level="medium")
self.assertIn("Unsupported thinking level 'medium'", result)
```

Then the sweep (three tool signatures, docstrings, the server's self-describing `get_help`

, every doc that repeated the wrong values) and a rebuild + push of the published Docker image, which had been shipping the bug to anyone who pulled it.

| Before | After | |
|---|---|---|
| Live call with default params | HTTP 400, every time | 🟢 image saved |
`thinking_level="minimal"` / `"medium"`
|
Approved locally, rejected remotely | Rejected locally with the allowed values named |
| Test suite | 10/10 green (bug invisible) | 11 assertions incl. contract regression test |
Published image `xbill9/nb2lite-mcp`
|
Shipped the broken default | Rebuilt, pushed, verified live |

Elapsed time from first failure to fixed-image-on-Docker-Hub: about ten minutes — because the failing tool call came back as readable text (`Allowed values are: low, high`

) instead of a stack trace. Error messages that name the fix are half the debugging.

`DEMO_FAST=1 ./demo.sh`

).The whole project is built on Google AI, end to end:

`gemini-3.1-flash-lite-image`

`store=True`

+ `previous_interaction_id`

) are what make multi-turn image editing work: the demo's edit step adds a neon RAMEN sign to `LlmAgent`

on `gemini-2.5-flash`

`MCPToolset`

— Gemini calling Gemini, with the bug fix sitting in between.`Allowed values are: low, high`

) is what made this a ten-minute smash.`xbill9/nb2lite-mcp`
