My team's mission is to accelerate the developer journey from writing code to running secure AI workloads on Google Cloud. To help developers succeed, we focus on identifying their most pressing questions and building demos that provide straightforward, easy-to-implement solutions.
Recently, I was struck with inspiration when the new —to identify technical questions from Reddit, research them using official documentation, and draft detailed technical blogs. Dev Signal also provides custom visuals using layer so the agent remembers my specific preferences and blogging style.
By connecting my coding assistant, in just two days.
Whether you want to learn how to architect a complex multi-agent system with long term memory, leverage local and remote MCP servers for tool standardization, or write detailed Terraform scripts for secure Cloud Run deployment, I'll show you how!
If you'd rather dive straight into the code and explore it at your own pace, you can clone the repository – You'll build the "brain" of the system by implementing a root orchestrator and a team of specialized agents. You'll also integrate the – Before moving to the cloud, you'll synchronize the agent's components and verify its performance on your workstation. You'll use a dedicated test runner to simulate the full lifecycle of discovery, research, and multimodal creation, with a special focus on validating long-term memory persistence by connecting your local agent directly to the cloud-based Vertex AI memory bank.
(
gcloud CLI) installed and authenticated.(required for the Reddit MCP tool).
You will also need:
- A : Vertex AI, Cloud Run, Secret Manager, Artifact Registry.
Reddit API Credentials (Client ID, Secret) - You can get these from the .
Project Setup
The Dev Signal system was built by first running the by . This foundation provided the project's modular directory structure, which is used to separate concerns between Agent Logic, Server Code, Utilities, and Tools.
The starter pack acts as a powerful starting point because it automates the creation of professional infrastructure, CI/CD pipelines, and observability tools in seconds. This allows you to focus entirely on the agent's unique intelligence while ensuring the underlying platform remains secure and scalable. By building on top of this generated boilerplate with AI assistance from , the development process is highly accelerated.
The agent starter pack high level architecture:
to standardize this. The Model Context Protocol (MCP) is a universal standard for connecting AI agents to external data and tools. Instead of writing custom API wrappers, we use standard MCP servers. This allows us to connect to APIs (Reddit), Knowledge Bases (Google Cloud Docs), and even local scripts (Image Generation using Nano Banana Pro) using a common interface. Create a new directory for the agent tools.
mkdir tools
cd tools
Tools Configuration
We'll define our toolsets in dev_signal_agent/tools/mcp_config.py.
This file defines the connection parameters for our three main tools.
Reddit: Connected via a local stdio subprocess.
Developer Knowledge: Connected via a remote HTTP endpoint.
Nano Banana: Connected via a local stdio subprocess (our custom Python script).
Reddit Search (Discovery Tool)
The provides grounding for your agent by allowing it to search the entire corpus of official Google Cloud documentation. Unlike the local Reddit server, this is a managed service hosted by Google and accessed as a remote endpoint over the internet. It exposes specialized tools like google_developer_documentation_search for semantic queries and google_developer_documentation_fetch to retrieve full markdown content, ensuring that every technical claim the agent makes is supported by definitive, up-to-date facts.
Note: You can also connect your coding assistant tools such as to the developer knowledge MCP server to empower them with handy up to date Google Cloud documentation. I used it when writing this blog!
To connect, the agent uses the McpToolset class with StreamableHTTPConnectionParams, pointing to a web URL instead of launching a local process. It securely authenticates using a DK_API_KEY (: We use the fastmcp library to drastically simplify server creation, allowing us to register Python functions as tools with just a few lines of code.
Gemini Integration: The server uses the Google GenAI SDK to call the
gemini-3-pro-image-preview model, which converts the agent's descriptive prompts into raw image bytes.GCS Upload & Hosting: Because agent interfaces typically require a URL to display images, the server automatically uploads the generated bytes to Google Cloud Storage (GCS) and returns a public link.
To connect this local tool, we use StdioConnectionParams because the server runs as a local subprocess communicating via standard input and output. This transport method directly matches the transport="stdio" configuration we will define in our server entrypoint, ensuring a seamless connection for your custom local scripts.
The following code defines the MCP connection in dev_signal_agent/tools/mcp_config.py. We use uv run to ensure the server starts in an isolated environment with all its dependencies correctly installed.
Paste this code in dev_signal_agent/tools/mcp_config.py:
def get_nano_banana_mcp_toolset():
"""
Connects to our local 'Nano Banana' image generator.
This demonstrates how to wrap a local Python script as an MCP tool.
"""
path = os.path.join("dev_signal_agent", "tools", "nano_banana_mcp", "main.py")
bucket = os.getenv("AI_ASSETS_BUCKET")
return McpToolset(
connection_params=StdioConnectionParams(
server_params=StdioServerParameters(
command="uv",
args=["run", path],
env={**os.environ, "AI_ASSETS_BUCKET": bucket}
),
timeout=600.0 # Image generation can take time
)
)
Implementing the Nano Banana Pro Server Logic
Now, we will implement the actual logic for this server. This implementation is based on the by Remigiusz Samborski. While Remi's original code provides instructions for deploying the MCP server to Cloud Run, we will run it here as a local subprocess for faster development and testing.
To get started, create the directory for our new server:
mkdir -p dev_signal_agent/tools/nano_banana_mcp
cd dev_signal_agent/tools/nano_banana_mcp
The Server Entrypoint (main.py)
This file acts as the "brain" that initializes and starts the MCP server.
FastMCP Initialization: We use theFastMCPlibrary to create a server named "MediaGenerators" and register ourgenerate_imagefunction as a tool.
Safe Logging: The_initialize_console_loggingfunction is critical. It forces all logs tosys.stderr. This is because the MCP "stdio" transport usessys.stdoutfor communication between the agent and the tool; standard logs sent tostdoutwould corrupt that protocol.
Execution: Themcp.run(transport="stdio")line starts the server as a local subprocess, allowing it to listen for requests from your agent via standard input.
Paste this code in dev_signal_agent/tools/nano_banana_mcp/main.py:
import logging
import os
import sys
from fastmcp import FastMCP
from dotenv import load_dotenv
from nano_banana_pro import generate_image
def _initialize_console_logging(min_level: int = logging.INFO):
# Ensure logs go to STDERR so they don't break the MCP stdio protocol
handler = logging.StreamHandler(sys.stderr)
logging.basicConfig(level=min_level, handlers=[handler], force=True)
tools = [generate_image]
mcp = FastMCP(name="MediaGenerators", tools=tools)
if __name__ == "__main__":
load_dotenv()
_initialize_console_logging()
mcp.run(transport="stdio")
The Generation Logic (nano_banana_pro.py)
This is where the actual image generation happens using Gemini.
GenAI Client: We initialize thegenai.Client()to interact with Google's generative models.
Model Selection: It specifically targets thegemini-3-pro-image-previewmodel. We set theresponse_modalitiesto "IMAGE" to tell the model we want pixels, not just text.
Robustness: The code includes aMAX_RETRIESloop (set to 5) to handle any transient generation errors, ensuring the agent has multiple attempts to get a valid image.
Byte Processing: Once the model generates the image, it arrives as raw inline data. We extract these bytes and call our helper to move them to the cloud.
URI Conversion: Finally, it replaces the internalgs://path with a browser-accessiblehttps://URL so the user can actually see the image.
Paste this code in dev_signal_agent/tools/nano_banana_mcp/nano_banana_pro.py:
import logging
from typing import Literal, Optional
from google import genai
from google.genai import types
from media_models import MediaAsset
from storage_utils import upload_data_to_gcs
AUTHORIZED_URI = "https://storage.mtls.cloud.google.com/"
MAX_RETRIES = 5
async def generate_image(
prompt: str,
aspect_ratio: Literal["16:9", "9:16"] = "16:9",
) -> MediaAsset:
"""Generates an image using Gemini 3 Image model."""
genai_client = genai.Client()
content = types.Content(parts=[types.Part.from_text(text=prompt)], role="user")
logging.info(f"Starting image generation for prompt: {prompt[:50]}...")
asset = MediaAsset(uri="")
for _ in range(MAX_RETRIES):
response = genai_client.models.generate_content(
model="gemini-3-pro-image-preview",
contents=[content],
config=types.GenerateContentConfig(
response_modalities=["IMAGE"],
image_config=types.ImageConfig(aspect_ratio=aspect_ratio)
)
)
if response and response.parts:
for part in response.parts:
if part.inline_data and part.inline_data.data:
# Upload the raw bytes to GCS
gcs_uri = await upload_data_to_gcs(
"mcp-tools",
part.inline_data.data,
part.inline_data.mime_type
)
asset = MediaAsset(uri=gcs_uri)
break
if asset.uri: break
if not asset.uri:
asset.error = "No image was generated."
else:
# Convert gs:// URI to an HTTP accessible URL if needed
asset.uri = asset.uri.replace('gs://', AUTHORIZED_URI)
logging.info(f"Image URL: {asset.uri}")
return asset
GCS Upload Helper (storage_utils.py)
Since agents need a web link to display images, this utility handles the hosting on Google Cloud Storage (GCS).
Dynamic Bucket Selection: It looks for a bucket name in your environment variables, falling back fromAI_ASSETS_BUCKETtoLOGS_BUCKET_NAMEto ensure it always has a place to save data.
Unique Filenames: We use an MD5 hash of the raw image data to create a unique filename. This prevents filename collisions and acts as a simple way to avoid duplicate uploads of the same image.
Cloud Upload: Theblob.upload_from_stringmethod pushes the raw image bytes directly to your GCS bucket.
Paste this code in dev_signal_agent/tools/nano_banana_mcp/storage_utils.py:
import hashlib
import mimetypes
import os
from google.cloud.storage import Client, Blob
from dotenv import load_dotenv
load_dotenv()
storage_client = Client()
ai_bucket_name = os.environ.get("AI_ASSETS_BUCKET") or os.environ.get("LOGS_BUCKET_NAME")
ai_bucket = storage_client.bucket(ai_bucket_name)
async def upload_data_to_gcs(agent_id: str, data: bytes, mime_type: str) -> str:
file_name = hashlib.md5(data).hexdigest()
ext = mimetypes.guess_extension(mime_type) or ""
blob_name = f"assets/{agent_id}/{file_name}{ext}"
blob = Blob(bucket=ai_bucket, name=blob_name)
blob.upload_from_string(data, content_type=mime_type, client=storage_client)
return f"gs://{ai_bucket_name}/{blob_name}"
Data Model (media_models.py)
This file ensures that our data follows a strict structure (Schema).
Structured Output: By using a PydanticBaseModel, we guarantee that the tool always returns a consistent JSON object containing auri(the link) and an optionalerrormessage. This makes it much easier for the AI agent to understand and process the tool's result.
Paste this code in dev_signal_agent/tools/nano_banana_mcp/media_models.py:
from typing import Optional
from pydantic import BaseModel
class MediaAsset(BaseModel):
uri: str
error: Optional[str] = None
Tool Dependencies (requirements.txt)
While we use uv to run our code, a requirements.txt file remains essential because it defines the specific dependencies uv needs to install for the Nano Banana server to function. This provides the necessary "ingredients" to set up the isolated environment before the server starts.
This file lists the three core libraries required for this tool:
google-cloud-storage: Used for hosting the generated images on the cloud.
google-genai: Provides the logic for the Gemini 3 Pro image generation.
fastmcp: The framework that turns our Python script into a standardized MCP tool.
Paste this code in dev_signal_agent/tools/nano_banana_mcp/requirements.txt:
google-cloud-storage==3.6.*
google-genai==1.52.*
fastmcp==2.13.*
Summary
In this first part of our series, we focused on establishing the agent's core capabilities by standardizing its external integrations through the Model Context Protocol (MCP). We initialized the project using uv for high-speed dependency management and successfully configured three critical toolsets: Reddit for trend discovery, Google Cloud Docs for technical grounding, and a custom "Nano Banana" MCP server for multimodal image generation. By utilizing the Google ADK's McpToolset, we've abstracted away complex API logic into simple, plug-and-play modules, ensuring that our tools share a common interface that decouples integration from intelligence.
For a deeper look into our technical foundation, you can explore the to explore the framework's core capabilities.
With our toolset fully configured and ready for action, we can now move to , where we will show you how to test the agent locally to verify these components on your workstation. If you’d like to dive ahead, you can explore the complete code for the entire series in our for the helpful review and feedback on this article.
For more content like this, follow Shir on .
SOCIAL SHARE CARD GENERATOR