<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mukhtar Salim</title>
    <description>The latest articles on DEV Community by Mukhtar Salim (@geekslife).</description>
    <link>https://dev.to/geekslife</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4025075%2F8ea3809a-d660-484d-ac1c-8a8457288ba7.png</url>
      <title>DEV Community: Mukhtar Salim</title>
      <link>https://dev.to/geekslife</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/geekslife"/>
    <language>en</language>
    <item>
      <title>Building an Intelligent Image Analyzer with Google Cloud Vision API and Cloud Run</title>
      <dc:creator>Mukhtar Salim</dc:creator>
      <pubDate>Wed, 30 Sep 2026 12:40:46 +0000</pubDate>
      <link>https://dev.to/geekslife/building-an-intelligent-image-analyzer-with-google-cloud-vision-api-and-cloud-run-4c3</link>
      <guid>https://dev.to/geekslife/building-an-intelligent-image-analyzer-with-google-cloud-vision-api-and-cloud-run-4c3</guid>
      <description>&lt;p&gt;Modern digital platforms from e-commerce marketplaces to content hubs handle massive volumes of user-uploaded imagery daily. Manually reviewing images, writing catalog tags, and catching policy-violating content quickly becomes an operational bottleneck. While training custom computer vision models from scratch is an option, it requires massive labeled datasets, specialized hardware, and continuous model maintenance.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is an Image Analyzer?
&lt;/h2&gt;

&lt;p&gt;An Image Analyzer is a software system or application that processes visual data (digital images) to extract meaningful, structured information. Instead of treating an image as merely a collection of raw pixels or a static file, an analyzer uses computer vision techniques and machine learning models to "understand" what is inside the picture.&lt;/p&gt;

&lt;p&gt;Depending on the implementation, an image analyzer can perform several tasks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Object &amp;amp; Entity Detection:&lt;/strong&gt; Identifying items, landscapes, people, or products.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Semantic Labeling:&lt;/strong&gt; Generating descriptive keywords and tagging the image (e.g., identifying "beach", "ocean", or "sunset").&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Content Moderation:&lt;/strong&gt; Detecting unsafe, sensitive, or policy-violating imagery (e.g., adult content, violence, or hate symbols).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Metadata Extraction:&lt;/strong&gt; Extracting dominant color palettes, aspect ratios, image clarity, and visual properties.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is Google Cloud Vision API?
&lt;/h2&gt;

&lt;p&gt;Google Cloud Vision API is a fully managed computer vision service provided by Google Cloud. It allows developers to integrate pre-trained machine learning models into their applications through simple REST or gRPC API calls.&lt;/p&gt;

&lt;p&gt;Training computer vision models from scratch requires massive datasets, expensive GPU compute clusters, and continuous model fine-tuning. Cloud Vision API eliminates this barrier by giving you immediate access to Google’s state-of-the-art vision models.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key capabilities include:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- Label Detection:&lt;/strong&gt; Generating descriptive tags with associated confidence scores.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- SafeSearch Detection:&lt;/strong&gt; Evaluating content likelihood across five moderation categories (&lt;code&gt;adult&lt;/code&gt;, &lt;code&gt;spoof&lt;/code&gt;, &lt;code&gt;medical&lt;/code&gt;, &lt;code&gt;violence&lt;/code&gt;, and &lt;code&gt;racy&lt;/code&gt;).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Text Detection (OCR): Extracting printed or handwritten text from images.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;- Landmark &amp;amp; Logo Recognition:&lt;/strong&gt; Identifying well-known public structures and commercial brand logos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- Image Properties:&lt;/strong&gt; Evaluating dominant colors, aspect ratios, and visual quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Google Cloud Run?
&lt;/h2&gt;

&lt;p&gt;Google Cloud Run is a fully managed serverless compute platform that runs containerized applications directly on top of Google’s scalable infrastructure.&lt;/p&gt;

&lt;p&gt;With Cloud Run, you package your code, dependencies, and system runtime into an OCI-compliant container (such as a standard Docker container). Cloud Run then manages everything else:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- True Serverless Scaling (Scale-to-Zero):&lt;/strong&gt; The service automatically spins up instances when requests arrive and scales down to zero when idle, meaning you do not pay for idle server compute.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- Granular Autoscaling:&lt;/strong&gt; When traffic spikes, Cloud Run provisions new instances in seconds and supports multiple concurrent requests per container instance.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zero Infrastructure Maintenance: There are no virtual machines to patch, no operating systems to update, and no Kubernetes clusters to configure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;- Native Security &amp;amp; IAM Integration:&lt;/strong&gt; Authenticates securely with other Google Cloud services using Service Accounts and Application Default Credentials (ADC), removing the need to embed secret API keys in your code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why We Need Them in This Demo
&lt;/h2&gt;

&lt;p&gt;In the Smart Tagger application, these three components work together to solve a specific production architecture challenge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ Image Upload ] ──&amp;gt; [ Cloud Run (FastAPI) ] ──&amp;gt; [ Cloud Vision API ] ──&amp;gt; [ Structured Tags &amp;amp; Flags ]
                      (Microservice Host)          (AI Inference Engine)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why an Image Analyzer?
&lt;/h2&gt;

&lt;p&gt;Modern applications (like e-commerce platforms, social feeds, and digital asset managers) receive thousands of user-uploaded images. Businesses cannot afford to have human operators manually verify every photo, write catalog search tags, and check for offensive material. An automated Image Analyzer is the core business solution being built.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Cloud Vision API?
&lt;/h2&gt;

&lt;p&gt;It provides the AI intelligence without requiring custom model training. It processes complex image labeling, dominant color extraction, and content moderation in a single network round-trip with high accuracy and low latency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Cloud Run?
&lt;/h2&gt;

&lt;p&gt;Cloud Vision API is an external service....it cannot serve user uploads or enforce business rules on its own. Cloud Run acts as the secure, scalable host for your API microservice (built with FastAPI). It:&lt;/p&gt;

&lt;p&gt;Provides an authenticated boundary between the client and Google Cloud using IAM (no exposed credentials).&lt;/p&gt;

&lt;p&gt;Validates incoming file types and size limits before invoking paid AI calls.&lt;/p&gt;

&lt;p&gt;Scales instantly to absorb sudden spikes in user uploads while scaling to zero during periods of inactivity, keeping operational costs low for developer communities and startups.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Getting Started&lt;/strong&gt;
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Accepts a user-uploaded photo.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Uses Vision API to generate hashtags automatically.&lt;/strong&gt; (This implies using an object/label detection or similar feature).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Filters out "unsafe" images before they are published.&lt;/strong&gt; (This uses the safe search detection feature of the Vision API).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To create this application, we need to build three main parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Frontend (HTML/CSS/JavaScript):&lt;/strong&gt; To allow users to upload a photo and display the results.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Backend (e.g., Python/Node.js):&lt;/strong&gt; To handle the file upload and communicate with the Google Cloud Vision API.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Google Cloud Project &amp;amp; Vision API:&lt;/strong&gt; Set up the necessary cloud resources and credentials.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Application Blueprint: The "Smart Tagger"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A. Prerequisites (What you need to set up)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A Google Cloud Project:&lt;/strong&gt; Create a new project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable the Vision API:&lt;/strong&gt; Go to "APIs &amp;amp; Services" and enable the "Cloud Vision API."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set up Authentication:&lt;/strong&gt; For a server application, the best method is to create a &lt;strong&gt;Service Account&lt;/strong&gt; and download the JSON key file. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;1. You will need to set an environment variable to point to this file&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;e.g., &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GOOGLEAPPLICATIONCREDENTIALS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/path/to/your/keyfile.json"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Install Python Libraries:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;1.  pip &lt;span class="nb"&gt;install &lt;/span&gt;Flask google-cloud-vision
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Backend Code (Python using Flask)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create a file named app.py. This code handles the upload, calls the Vision API, and checks for safety.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Python&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;import os

from flask import Flask, request, jsonify, render&lt;span class="se"&gt;\_&lt;/span&gt;template

from google.cloud import vision
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Initialize Flask app&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;app &lt;span class="o"&gt;=&lt;/span&gt; Flask&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="se"&gt;\_\_&lt;/span&gt;name&lt;span class="se"&gt;\_\_&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Vision API Functions&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;def analyze_image_with_vision&lt;span class="o"&gt;(&lt;/span&gt;image_path&lt;span class="o"&gt;)&lt;/span&gt;:
    &lt;span class="s2"&gt;"""Detects labels and checks for explicit content in the image."""&lt;/span&gt;

    client &lt;span class="o"&gt;=&lt;/span&gt; vision.ImageAnnotatorClient&lt;span class="o"&gt;()&lt;/span&gt;

    with open&lt;span class="o"&gt;(&lt;/span&gt;image_path, &lt;span class="s1"&gt;'rb'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; as image_file:
        content &lt;span class="o"&gt;=&lt;/span&gt; image_file.read&lt;span class="o"&gt;()&lt;/span&gt;

    image &lt;span class="o"&gt;=&lt;/span&gt; vision.Image&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;content&lt;span class="o"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;# 1. Label Detection for Hashtags&lt;/span&gt;
    label_response &lt;span class="o"&gt;=&lt;/span&gt; client.label_detection&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;image&lt;span class="o"&gt;)&lt;/span&gt;
    labels &lt;span class="o"&gt;=&lt;/span&gt; label_response.label_annotations

    hashtags &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;
        f&lt;span class="s2"&gt;"#{label.description.replace(' ', '').replace('/', '')}"&lt;/span&gt;
        &lt;span class="k"&gt;for &lt;/span&gt;label &lt;span class="k"&gt;in &lt;/span&gt;labels[:5]
    &lt;span class="o"&gt;]&lt;/span&gt;  &lt;span class="c"&gt;# Take top 5 labels&lt;/span&gt;

    &lt;span class="c"&gt;# 2. Safe Search Detection for Filtering&lt;/span&gt;
    safe_response &lt;span class="o"&gt;=&lt;/span&gt; client.safe_search_detection&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;image&lt;span class="o"&gt;)&lt;/span&gt;
    safe &lt;span class="o"&gt;=&lt;/span&gt; safe_response.safe_search_annotation

    &lt;span class="c"&gt;# Check for likely or very likely unsafe content&lt;/span&gt;
    is_unsafe &lt;span class="o"&gt;=&lt;/span&gt; False

    &lt;span class="c"&gt;# Vision API Safe Search categories and their thresholds&lt;/span&gt;
    &lt;span class="c"&gt;# for "Unsafe"&lt;/span&gt;
    &lt;span class="c"&gt;# LIKELY (3) or VERY_LIKELY (4) are generally considered unsafe.&lt;/span&gt;
    unsafe_threshold &lt;span class="o"&gt;=&lt;/span&gt; 3

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
        safe.adult &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; unsafe_threshold
        or safe.violence &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; unsafe_threshold
        or safe.racy &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; unsafe_threshold
        or safe.medical &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; unsafe_threshold
    &lt;span class="o"&gt;)&lt;/span&gt;:
        is_unsafe &lt;span class="o"&gt;=&lt;/span&gt; True

    &lt;span class="k"&gt;return &lt;/span&gt;hashtags, is_unsafe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Flask Routes&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;@app.route&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

def index&lt;span class="o"&gt;()&lt;/span&gt;:

    &lt;span class="s2"&gt;"""Renders the upload page."""&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;render&lt;span class="se"&gt;\_&lt;/span&gt;template&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'index.html'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

@app.route&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/upload'&lt;/span&gt;, &lt;span class="nv"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="se"&gt;\[&lt;/span&gt;&lt;span class="s1"&gt;'POST'&lt;/span&gt;&lt;span class="se"&gt;\]&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

def upload&lt;span class="se"&gt;\_&lt;/span&gt;file&lt;span class="o"&gt;()&lt;/span&gt;:

    &lt;span class="s2"&gt;"""Handles the file upload and analysis."""&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s1"&gt;'file'&lt;/span&gt; not &lt;span class="k"&gt;in &lt;/span&gt;request.files:

        &lt;span class="k"&gt;return &lt;/span&gt;jsonify&lt;span class="o"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'error'&lt;/span&gt;: &lt;span class="s1"&gt;'No file part'&lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;, 400

    file &lt;span class="o"&gt;=&lt;/span&gt; request.files&lt;span class="se"&gt;\[&lt;/span&gt;&lt;span class="s1"&gt;'file'&lt;/span&gt;&lt;span class="se"&gt;\]&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;file.filename &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;:

        &lt;span class="k"&gt;return &lt;/span&gt;jsonify&lt;span class="o"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'error'&lt;/span&gt;: &lt;span class="s1"&gt;'No selected file'&lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;, 400

    &lt;span class="k"&gt;if &lt;/span&gt;file:

        &lt;span class="c"&gt;# Save the file temporarily&lt;/span&gt;

        temp&lt;span class="se"&gt;\_&lt;/span&gt;file&lt;span class="se"&gt;\_&lt;/span&gt;path &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'temp\_uploaded\_image.jpg'&lt;/span&gt;

        file.save&lt;span class="o"&gt;(&lt;/span&gt;temp&lt;span class="se"&gt;\_&lt;/span&gt;file&lt;span class="se"&gt;\_&lt;/span&gt;path&lt;span class="o"&gt;)&lt;/span&gt;

        try:

            &lt;span class="c"&gt;# Analyze the image&lt;/span&gt;

            hashtags, is&lt;span class="se"&gt;\_&lt;/span&gt;unsafe &lt;span class="o"&gt;=&lt;/span&gt; analyze&lt;span class="se"&gt;\_&lt;/span&gt;image&lt;span class="se"&gt;\_&lt;/span&gt;with&lt;span class="se"&gt;\_&lt;/span&gt;vision&lt;span class="o"&gt;(&lt;/span&gt;temp&lt;span class="se"&gt;\_&lt;/span&gt;file&lt;span class="se"&gt;\_&lt;/span&gt;path&lt;span class="o"&gt;)&lt;/span&gt;

            &lt;span class="c"&gt;# Prepare result&lt;/span&gt;

            result &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

                &lt;span class="s1"&gt;'status'&lt;/span&gt;: &lt;span class="s1"&gt;'success'&lt;/span&gt;,

                &lt;span class="s1"&gt;'hashtags'&lt;/span&gt;: hashtags,

                &lt;span class="s1"&gt;'is\_unsafe'&lt;/span&gt;: is&lt;span class="se"&gt;\_&lt;/span&gt;unsafe,

                &lt;span class="s1"&gt;'message'&lt;/span&gt;: &lt;span class="s1"&gt;'Image analyzed successfully.'&lt;/span&gt;

            &lt;span class="o"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;if &lt;/span&gt;is&lt;span class="se"&gt;\_&lt;/span&gt;unsafe:

                result&lt;span class="se"&gt;\[&lt;/span&gt;&lt;span class="s1"&gt;'message'&lt;/span&gt;&lt;span class="se"&gt;\]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'⚠️ \*\*UNSAFE IMAGE DETECTED.\*\* Filtering image from publication.'&lt;/span&gt;

                result&lt;span class="se"&gt;\[&lt;/span&gt;&lt;span class="s1"&gt;'hashtags'&lt;/span&gt;&lt;span class="se"&gt;\]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="se"&gt;\[\]&lt;/span&gt; &lt;span class="c"&gt;# Clear hashtags if unsafe&lt;/span&gt;

            &lt;span class="k"&gt;else&lt;/span&gt;:

                result&lt;span class="se"&gt;\[&lt;/span&gt;&lt;span class="s1"&gt;'message'&lt;/span&gt;&lt;span class="se"&gt;\]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'✅ Image is safe. Hashtags generated.'&lt;/span&gt;

            &lt;span class="k"&gt;return &lt;/span&gt;jsonify&lt;span class="o"&gt;(&lt;/span&gt;result&lt;span class="o"&gt;)&lt;/span&gt;

        except Exception as e:

            &lt;span class="k"&gt;return &lt;/span&gt;jsonify&lt;span class="o"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;: &lt;span class="s1"&gt;'error'&lt;/span&gt;, &lt;span class="s1"&gt;'message'&lt;/span&gt;: f&lt;span class="s1"&gt;'An error occurred during analysis: {e}'&lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;, 500

        finally:

            &lt;span class="c"&gt;# Clean up the temporary file&lt;/span&gt;

            &lt;span class="k"&gt;if &lt;/span&gt;os.path.exists&lt;span class="o"&gt;(&lt;/span&gt;temp&lt;span class="se"&gt;\_&lt;/span&gt;file&lt;span class="se"&gt;\_&lt;/span&gt;path&lt;span class="o"&gt;)&lt;/span&gt;:

                os.remove&lt;span class="o"&gt;(&lt;/span&gt;temp&lt;span class="se"&gt;\_&lt;/span&gt;file&lt;span class="se"&gt;\_&lt;/span&gt;path&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="se"&gt;\_\_&lt;/span&gt;name&lt;span class="se"&gt;\_\_&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'\_\_main\_\_'&lt;/span&gt;:

    &lt;span class="c"&gt;# You must have the GOOGLE\_APPLICATION\_CREDENTIALS environment variable set&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;not os.environ.get&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'GOOGLE\_APPLICATION\_CREDENTIALS'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:

        print&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"!!! WARNING: GOOGLE&lt;/span&gt;&lt;span class="se"&gt;\_&lt;/span&gt;&lt;span class="s2"&gt;APPLICATION&lt;/span&gt;&lt;span class="se"&gt;\_&lt;/span&gt;&lt;span class="s2"&gt;CREDENTIALS environment variable is not set. !!!"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

        print&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Please set it to the path of your Vision API Service Account key file."&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

    app.run&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;True&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3 Frontend Code (HTML/JavaScript)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create a folder named templates and inside it, a file named index.html. This provides the user interface.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Demo: The "Smart Tagger"&lt;/strong&gt;&lt;br&gt;
A simple web application that automates image tagging and safety filtering using the Vision API.&lt;/p&gt;

&lt;p&gt;Analyze Image&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analysis Results&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Upload an image to start the analysis.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;function &lt;/span&gt;uploadImage&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    const fileInput &lt;span class="o"&gt;=&lt;/span&gt; document.getElementById&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'imageUpload'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    const file &lt;span class="o"&gt;=&lt;/span&gt; fileInput.files[0]&lt;span class="p"&gt;;&lt;/span&gt;
    const statusMessage &lt;span class="o"&gt;=&lt;/span&gt; document.getElementById&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'statusMessage'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    const safetyBadge &lt;span class="o"&gt;=&lt;/span&gt; document.getElementById&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'safetyBadge'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    const hashtagsDisplay &lt;span class="o"&gt;=&lt;/span&gt; document.getElementById&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'hashtagsDisplay'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    // Reset previous results
    statusMessage.textContent &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Analyzing...'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    safetyBadge.className &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    safetyBadge.textContent &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    hashtagsDisplay.innerHTML &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;file&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        statusMessage.textContent &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Please select a file first.'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    const formData &lt;span class="o"&gt;=&lt;/span&gt; new FormData&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    formData.append&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'file'&lt;/span&gt;, file&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    fetch&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/upload'&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;
        method: &lt;span class="s1"&gt;'POST'&lt;/span&gt;,
        body: formData
    &lt;span class="o"&gt;})&lt;/span&gt;
        .then&lt;span class="o"&gt;(&lt;/span&gt;response &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; response.json&lt;span class="o"&gt;())&lt;/span&gt;
        .then&lt;span class="o"&gt;(&lt;/span&gt;data &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            statusMessage.textContent &lt;span class="o"&gt;=&lt;/span&gt; data.message&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;data.status &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'success'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;data.is_unsafe&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                    safetyBadge.className &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'unsafe'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    safetyBadge.textContent &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Status: UNSAFE (Filtered)'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                    safetyBadge.className &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'safe'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    safetyBadge.textContent &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Status: SAFE (Published)'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

                    data.hashtags.forEach&lt;span class="o"&gt;(&lt;/span&gt;tag &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                        const span &lt;span class="o"&gt;=&lt;/span&gt; document.createElement&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'span'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                        span.textContent &lt;span class="o"&gt;=&lt;/span&gt; tag&lt;span class="p"&gt;;&lt;/span&gt;
                        hashtagsDisplay.appendChild&lt;span class="o"&gt;(&lt;/span&gt;span&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                safetyBadge.className &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'unsafe'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                safetyBadge.textContent &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'ERROR'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                console.error&lt;span class="o"&gt;(&lt;/span&gt;data.message&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;})&lt;/span&gt;
        .catch&lt;span class="o"&gt;(&lt;/span&gt;error &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            statusMessage.textContent &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;An error occurred: &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.message&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            safetyBadge.className &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'unsafe'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            safetyBadge.textContent &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'NETWORK ERROR'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            console.error&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Fetch error:'&lt;/span&gt;, error&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;4 How to Run the Application&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Set Environment Variable:&lt;/strong&gt; Open your terminal and set the path to your service account key file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GOOGLEAPPLICATIONCREDENTIALS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/path/to/your/vision-api-key.json"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Run the Flask App:&lt;/strong&gt; In the directory where you saved app.py, run:python &lt;a href="http://app.py" rel="noopener noreferrer"&gt;app.py&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Open in Browser:&lt;/strong&gt; Open your web browser and navigate to the address shown in the terminal (usually &lt;a href="http://127.0.0.1:5000/" rel="noopener noreferrer"&gt;http://127.0.0.1:5000/&lt;/a&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>ai</category>
      <category>cloud</category>
      <category>machinelearning</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Building Production-Ready, Secure AI Backends with WebMCP and Google Cloud Run</title>
      <dc:creator>Mukhtar Salim</dc:creator>
      <pubDate>Thu, 10 Sep 2026 06:17:47 +0000</pubDate>
      <link>https://dev.to/geekslife/building-production-ready-secure-ai-backends-with-webmcp-and-google-cloud-run-5ami</link>
      <guid>https://dev.to/geekslife/building-production-ready-secure-ai-backends-with-webmcp-and-google-cloud-run-5ami</guid>
      <description>&lt;p&gt;The shift toward autonomous AI agents has exposed a critical gap in modern web infrastructure: the interface gap. For years, browser-based AI agents have had to navigate web pages the way humans do...parsing visual layouts, guessing CSS selectors, and attempting to mimic clicks.&lt;/p&gt;

&lt;p&gt;Connecting in-browser agent tools directly to modern cloud backends requires an architectural shift. By combining WebMCP on the frontend with Google Cloud Run on the backend, engineers can build a deterministic, resilient, and enterprise-grade AI foundation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is WebMCP?
&lt;/h2&gt;

&lt;p&gt;WebMCP (Web Model Context Protocol) is an emerging browser-level standard that allows live web pages to expose structured, machine-readable tools directly to client-side AI agents.  &lt;/p&gt;

&lt;p&gt;While Anthropic’s open-source MCP focuses on server-to-server and local-system connections (connecting LLMs to local databases, file systems, and internal enterprise services), WebMCP brings that same tooling contract into the browser DOM.  &lt;/p&gt;

&lt;p&gt;Through imperative JavaScript APIs (or declarative HTML annotations), a web application can register explicit capabilities...defining function names, JSON input schemas, and expected returns. When an AI agent visits the page, it does not need to guess what a button does; it inspects the page's registered WebMCP tools and calls them natively.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does It Matter?
&lt;/h2&gt;

&lt;p&gt;Traditional web agents rely on heuristic scraping, computer vision screenshot analysis, and DOM parsing. This approach introduces major engineering bottlenecks:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- Fragility:&lt;/strong&gt; A slight redesign, a changed CSS class name, or an A/B test often breaks the agent's workflow entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- Token Inefficiency:&lt;/strong&gt; Feeding raw HTML or multi-resolution screenshots into LLM context windows consumes thousands of tokens per step, escalating cost and latency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- Non-Determinism:&lt;/strong&gt; Vision-driven clicking frequently misfires on interactive widgets, dynamic modals, or custom calendar pickers.&lt;/p&gt;

&lt;p&gt;WebMCP establishes an explicit contract between websites and AI. Instead of asking an LLM to find an &lt;code&gt;&amp;lt;input name="search"&amp;gt;&lt;/code&gt; tag and trigger a mouse event, the page declares a search_flights tool with typed arguments. The agent interacts with the website via a reliable, typed contract, making browser automation resilient and predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Use WebMCP Instead of a Normal API?
&lt;/h2&gt;

&lt;p&gt;WebMCP is an in-context orchestration layer for browser-agent interaction rather than a replacement for standard REST or GraphQL APIs. Unlike headless, server-to-server APIs that operate blindly and require manual state assembly and separate authentication flows, WebMCP executes inside the user's active browser session. &lt;/p&gt;

&lt;p&gt;This setup enables tools to inherit live DOM state, reuse existing login sessions, and register dynamically based on application context. Most importantly, it keeps humans in the loop by updating the UI in real time, allowing users to visibly monitor, confirm, or abort agent actions as they happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Google Cloud Run?
&lt;/h2&gt;

&lt;p&gt;Google Cloud Run is a fully managed, serverless compute platform that enables you to deploy and run containerized applications directly on top of Google’s scalable infrastructure.&lt;/p&gt;

&lt;p&gt;Cloud Run eliminates container orchestration complexity. You package your application logic...whether written in Python, Go, Node.js, or Rust...into an OCI-compliant container image, and Cloud Run provisions, executes, and scales it automatically in response to incoming HTTPS requests or asynchronous events.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Features of Google Cloud Run
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;- True Serverless Scaling (Scale-to-Zero):&lt;/strong&gt; Cloud Run instances scale dynamically from zero to thousands based on incoming traffic, meaning you only pay for compute resources while requests are actively processing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- Concurrency per Instance:&lt;/strong&gt; Unlike traditional Function-as-a-Service (FaaS) platforms where one instance handles one request at a time, Cloud Run allows a single container instance to process dozens of concurrent requests, optimizing memory and reducing costs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- Native Secret Management:&lt;/strong&gt; Deep integration with Google Cloud Secret Manager allows API keys (such as GEMINI_API_KEY) to be injected directly as environment variables or mounted files without hardcoding credentials in container builds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- Built-in Identity &amp;amp; Access Management (IAM):&lt;/strong&gt; Native support for Google Cloud IAM enables granular access policies, ensuring that only authenticated users or approved API Gateways can invoke endpoints.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- Traffic Splitting &amp;amp; Canary Deployments:&lt;/strong&gt; Cloud Run allows zero-downtime rollouts and traffic splitting across multiple container revisions (e.g., routing 10% of agent traffic to a new LLM model prompt for evaluation).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- VPC Integration:&lt;/strong&gt; Containers can securely communicate with internal databases (like Cloud SQL or Memorystore) and private VPC networks without exposing resources to the public internet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pros of Using Google Cloud Run for AI Backends
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;- Reduced Operational Overhead:&lt;/strong&gt; No virtual machines to patch, no Kubernetes clusters to configure, and no manual node autoscaling policies to maintain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- Cost Predictability:&lt;/strong&gt; For AI workloads with unpredictable spikes or long periods of developer inactivity, scaling to zero prevents idle server bills.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- Portability:&lt;/strong&gt; Because Cloud Run is standard container-based (Docker/Podman), you avoid vendor lock-in. The same container running on Cloud Run can run locally via Docker Compose or on an on-premises cluster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- Long Request Timeouts:&lt;/strong&gt; AI workloads....especially long-form analysis, retrieval-augmented generation (RAG), and agentic reasoning loops....require longer runtimes. Cloud Run supports request timeouts up to 60 minutes, accommodating long-running generative processes without dropped connections.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- Hardware Acceleration (GPU Support):&lt;/strong&gt; Cloud Run supports attached GPUs (such as NVIDIA L4s), allowing you to run self-hosted inference or embedding models serverlessly alongside lightweight orchestration APIs.7. &lt;/p&gt;

&lt;h2&gt;
  
  
  Why Build a Production-Ready, Secure AI Backend with WebMCP and Google Cloud Run?
&lt;/h2&gt;

&lt;p&gt;While running WebMCP with a local server (localhost:8080) is ideal for developer experimentation, a production deployment presents serious enterprise requirements: session isolation, API key protection, denial-of-service resilience, and strict compliance.&lt;/p&gt;

&lt;p&gt;Pairing WebMCP and Google Cloud Run creates an optimal AI architecture for several reasons:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A. Zero Secret Exposure on the Client&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;WebMCP runs on the user's browser, meaning client-side code is entirely public. If a browser tool attempts to call models like Google Gemini directly from JavaScript, the API key must live in the browser, exposing it to exfiltration.&lt;/p&gt;

&lt;p&gt;By having the WebMCP tool forward requests to a FastAPI container on Cloud Run, Cloud Run acts as a secure boundary. The Gemini API key remains isolated inside Google Secret Manager, never visible to the browser or network inspector.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;B. Defending Against Resource Exhaustion &amp;amp; Model Quota Draining&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because WebMCP allows autonomous or semi-autonomous tools to trigger operations, unchecked clients could inadvertently loop and consume your LLM token quotas.&lt;/p&gt;

&lt;p&gt;Placing Cloud Run behind an API Gateway or Cloud Armor allows you to implement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Granular IP and token-bucket rate limiting.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Input size and schema validation (using Pydantic/FastAPI) before calling upstream models.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Token budgets and payload sanitization to mitigate prompt injection.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;C. Enterprise-Grade Session Security&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;WebMCP leverages the user’s active web session for frontend context, while Cloud Run allows you to enforce strict CORS policies and JWT/OAuth authentication. The backend verifies that the incoming request originates from your authorized frontend domain and corresponds to a validated, logged-in enterprise user before executing backend agent tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;D. Architectural Symmetry&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This architecture creates a clean separation of concerns:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- The Client (WebMCP):&lt;/strong&gt; Governs user experience, contextual state, DOM-aware interactions, and transparent human-in-the-loop controls. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- The Cloud (Cloud Run):&lt;/strong&gt; Governs compute, security boundaries, model orchestration, cost controls, and audit logging.&lt;/p&gt;

&lt;p&gt;By adopting this pattern, teams can deploy AI agents that operate reliably inside the browser without compromising enterprise security or server stability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hands-On: Building the WebMCP Gemini Data Analyzer
&lt;/h2&gt;

&lt;p&gt;This practical proof-of-concept shows how an in-browser WebMCP tool registers an analytical capability, accepts parameters from an agent, and delegates execution to a containerized FastAPI service on Cloud Run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture Flow&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;index.html serves as the frontend and registers the &lt;code&gt;analyze_data_with_cloud_run&lt;/code&gt; tool via WebMCP.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The browser agent inspects the page, discovers the tool, and passes an analytical prompt with structured JSON.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The WebMCP handler sends a POST request to FastAPI's /analyze endpoint.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;FastAPI validates the payload, queries Gemini via the official SDK, and returns the response.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The frontend displays the analysis live in the DOM for the user.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;The application has two independent parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;index.html&lt;/code&gt; registers a WebMCP tool named &lt;code&gt;analyze_data_with_cloud_run&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A browser agent discovers and invokes that tool with a prompt and JSON data.&lt;/li&gt;
&lt;li&gt;The tool sends a &lt;code&gt;POST&lt;/code&gt; request to the FastAPI &lt;code&gt;/analyze&lt;/code&gt; endpoint.&lt;/li&gt;
&lt;li&gt;FastAPI sends the prompt and data to the Gemini API.&lt;/li&gt;
&lt;li&gt;Gemini's response is returned to the browser agent as JSON.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser agent
    |
    | WebMCP tool invocation
    v
index.html (localhost:5500)
    |
    | POST /analyze
    v
FastAPI (localhost:8080)
    |
    | Gemini generate_content
    v
Google Gemini API
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Project structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── Dockerfile
├── README.md
├── index.html
├── main.py
└── requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;index.html&lt;/code&gt; contains the user interface and WebMCP tool registration.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;main.py&lt;/code&gt; contains the FastAPI application and Gemini integration.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;requirements.txt&lt;/code&gt; lists the Python dependencies.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Dockerfile&lt;/code&gt; packages the backend for container-based deployment.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.11 or newer&lt;/li&gt;
&lt;li&gt;A Google Gemini API key&lt;/li&gt;
&lt;li&gt;Brave or Chrome with experimental WebMCP support&lt;/li&gt;
&lt;li&gt;Docker, optionally, for running the backend in a container&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Local setup
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Create a virtual environment
&lt;/h3&gt;

&lt;p&gt;Open a terminal in the project directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /Users/mukhtarsalim/Desktop/webmcp-demo
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows PowerShell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;venv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;venv&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;\.venv\Scripts\Activate.ps1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Install dependencies
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Configure the Gemini API key
&lt;/h3&gt;

&lt;p&gt;Create or rotate an API key in Google AI Studio. Export it only in the backend terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GEMINI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'your-new-gemini-api-key'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows PowerShell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;GEMINI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'your-new-gemini-api-key'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not add API keys to &lt;code&gt;index.html&lt;/code&gt;, source control, screenshots, documentation, or shell scripts.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Start the backend
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uvicorn main:app &lt;span class="nt"&gt;--host&lt;/span&gt; 0.0.0.0 &lt;span class="nt"&gt;--port&lt;/span&gt; 8080 &lt;span class="nt"&gt;--reload&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API should be available at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Health endpoint: &lt;code&gt;http://localhost:8080/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Analysis endpoint: &lt;code&gt;http://localhost:8080/analyze&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Swagger UI: &lt;code&gt;http://localhost:8080/docs&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep this terminal running.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Start the frontend
&lt;/h3&gt;

&lt;p&gt;Open a second terminal in the project directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; http.server 5500
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;http://localhost:5500&lt;/code&gt; in the WebMCP-enabled browser.&lt;/p&gt;

&lt;p&gt;Do not open &lt;code&gt;index.html&lt;/code&gt; directly with a &lt;code&gt;file://&lt;/code&gt; URL. WebMCP requires a secure context such as HTTPS or &lt;code&gt;localhost&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enable WebMCP
&lt;/h2&gt;

&lt;p&gt;WebMCP is currently an experimental browser feature.&lt;/p&gt;

&lt;p&gt;For Brave:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;code&gt;brave://flags/#enable-webmcp-testing&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Set &lt;strong&gt;WebMCP for testing&lt;/strong&gt; to &lt;strong&gt;Enabled&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Restart Brave.&lt;/li&gt;
&lt;li&gt;Open &lt;code&gt;http://localhost:5500&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Open the WebMCP Tools inspector on the application tab.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For Chrome:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;code&gt;chrome://flags/#enable-webmcp-testing&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Set &lt;strong&gt;WebMCP for testing&lt;/strong&gt; to &lt;strong&gt;Enabled&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Restart Chrome.&lt;/li&gt;
&lt;li&gt;Open &lt;code&gt;http://localhost:5500&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Use the Model Context Tool Inspector to view and invoke the registered tool.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The tool is registered by the application page, not globally. The inspector will not find the tool while the active page is &lt;code&gt;brave://flags&lt;/code&gt;, &lt;code&gt;chrome://flags&lt;/code&gt;, or another website.&lt;/p&gt;

&lt;h2&gt;
  
  
  WebMCP tool
&lt;/h2&gt;

&lt;p&gt;The frontend registers this tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;analyze_data_with_cloud_run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It accepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;prompt&lt;/code&gt;: the question or analysis instruction for Gemini.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;data&lt;/code&gt;: a JSON object containing the structured information to analyze.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example arguments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"prompt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Summarize the data and identify unusual values."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sales"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;135&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;410&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tool forwards the arguments to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST http://localhost:8080/analyze
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  API usage
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Health check
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:8080/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"online"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WebMCP Cloud Run Backend is running"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"api_key_configured"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Analyze data
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--request&lt;/span&gt; POST http://localhost:8080/analyze &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
    "prompt": "Summarize this dataset.",
    "data": {
      "value": 42,
      "category": "example"
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example successful response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Gemini-generated analysis"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Run the backend with Docker
&lt;/h2&gt;

&lt;p&gt;Build the image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; webmcp-demo &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--publish&lt;/span&gt; 8080:8080 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;GEMINI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'your-new-gemini-api-key'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  webmcp-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Docker container runs only the FastAPI backend. The frontend must still be served separately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; http.server 5500
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Optional Cloud Run deployment
&lt;/h2&gt;

&lt;p&gt;Cloud Run is not required for local development. It is only needed when the backend must be publicly accessible.&lt;/p&gt;

&lt;p&gt;For a hosted deployment:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build and deploy the backend container to Cloud Run.&lt;/li&gt;
&lt;li&gt;Configure &lt;code&gt;GEMINI_API_KEY&lt;/code&gt; as a Cloud Run secret or environment variable.&lt;/li&gt;
&lt;li&gt;Restrict unauthenticated access where appropriate.&lt;/li&gt;
&lt;li&gt;Configure CORS to allow only the deployed frontend origin.&lt;/li&gt;
&lt;li&gt;Replace the local backend URL in &lt;code&gt;index.html&lt;/code&gt; with the deployed HTTPS &lt;code&gt;/analyze&lt;/code&gt; URL.&lt;/li&gt;
&lt;li&gt;Serve the frontend from an HTTPS host.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Do not deploy the current demo publicly without adding authentication, rate limiting, request-size limits, and restrictive CORS settings. Otherwise, third parties could consume the Gemini quota.&lt;/p&gt;

&lt;h2&gt;
  
  
  Changes made during local setup
&lt;/h2&gt;

&lt;p&gt;The original frontend sent requests directly to a hardcoded Google Cloud Run endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://webmcp-backend-103973904738.us-central1.run.app/analyze
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It was changed to the local FastAPI endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:8080/analyze
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This change:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Removes the need to configure Cloud Run for local testing.&lt;/li&gt;
&lt;li&gt;Connects the locally served frontend to the locally running backend.&lt;/li&gt;
&lt;li&gt;Keeps Gemini API calls on the backend so the API key is not exposed to browser code.&lt;/li&gt;
&lt;li&gt;Allows the complete WebMCP flow to run on &lt;code&gt;localhost&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The application was then verified with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The FastAPI health endpoint responding on port &lt;code&gt;8080&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The frontend serving successfully on port &lt;code&gt;5500&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;WebMCP for testing enabled in Brave.&lt;/li&gt;
&lt;li&gt;The tool inspector opened on the application page.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  WebMCP API not found
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Confirm the WebMCP testing flag is enabled.&lt;/li&gt;
&lt;li&gt;Restart the browser after changing the flag.&lt;/li&gt;
&lt;li&gt;Open the application through &lt;code&gt;http://localhost:5500&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Test with the latest Google Chrome if Brave does not expose the API.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  No tools registered
&lt;/h3&gt;

&lt;p&gt;The inspector must be attached to the application tab. Navigate to &lt;code&gt;http://localhost:5500&lt;/code&gt; before opening or refreshing the inspector.&lt;/p&gt;

&lt;h3&gt;
  
  
  Receiving end does not exist
&lt;/h3&gt;

&lt;p&gt;This usually means the inspector extension cannot communicate with the active page or its background process.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Restart the browser.&lt;/li&gt;
&lt;li&gt;Reopen the application tab.&lt;/li&gt;
&lt;li&gt;Disable and re-enable the inspector extension.&lt;/li&gt;
&lt;li&gt;Test with current Google Chrome if the issue persists in Brave.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Backend is not reachable
&lt;/h3&gt;

&lt;p&gt;Confirm the backend process is running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:8080/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also confirm that &lt;code&gt;index.html&lt;/code&gt; points to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:8080/analyze
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Gemini API key is not configured
&lt;/h3&gt;

&lt;p&gt;Export &lt;code&gt;GEMINI_API_KEY&lt;/code&gt; in the same terminal before starting Uvicorn. Restart Uvicorn after changing it.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;python main.py&lt;/code&gt; fails
&lt;/h3&gt;

&lt;p&gt;The current &lt;code&gt;main.py&lt;/code&gt; calls &lt;code&gt;uvicorn.run(...)&lt;/code&gt; without importing &lt;code&gt;uvicorn&lt;/code&gt;. Start the application with the supported command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uvicorn main:app &lt;span class="nt"&gt;--host&lt;/span&gt; 0.0.0.0 &lt;span class="nt"&gt;--port&lt;/span&gt; 8080 &lt;span class="nt"&gt;--reload&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, add &lt;code&gt;import uvicorn&lt;/code&gt; at the top of &lt;code&gt;main.py&lt;/code&gt; before using &lt;code&gt;python main.py&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  CORS errors
&lt;/h3&gt;

&lt;p&gt;Ensure the frontend uses &lt;code&gt;http://localhost:5500&lt;/code&gt; and the backend uses &lt;code&gt;http://localhost:8080&lt;/code&gt;. For production, replace wildcard CORS configuration with the exact frontend origin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current limitations
&lt;/h2&gt;

&lt;p&gt;This project is a proof of concept rather than a production-ready service:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;/analyze&lt;/code&gt; endpoint has no authentication.&lt;/li&gt;
&lt;li&gt;CORS currently permits every origin.&lt;/li&gt;
&lt;li&gt;There is no rate limiting or request-size limit.&lt;/li&gt;
&lt;li&gt;Backend exception details may be returned to clients.&lt;/li&gt;
&lt;li&gt;Dependencies are not version-pinned.&lt;/li&gt;
&lt;li&gt;Automated tests and CI are not configured.&lt;/li&gt;
&lt;li&gt;The WebMCP tool does not yet use an &lt;code&gt;AbortSignal&lt;/code&gt; for cleanup.&lt;/li&gt;
&lt;li&gt;The WebMCP tool does not yet declare security annotations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Security notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Rotate an API key immediately if it appears in terminal history, screenshots, chat, or source control.&lt;/li&gt;
&lt;li&gt;Never put &lt;code&gt;GEMINI_API_KEY&lt;/code&gt; in frontend JavaScript.&lt;/li&gt;
&lt;li&gt;Use a secret manager for hosted deployments.&lt;/li&gt;
&lt;li&gt;Add authentication and rate limiting before exposing &lt;code&gt;/analyze&lt;/code&gt; publicly.&lt;/li&gt;
&lt;li&gt;Restrict CORS to trusted frontend origins in production.&lt;/li&gt;
&lt;li&gt;Avoid returning raw provider errors to clients.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Stopping the application
&lt;/h2&gt;

&lt;p&gt;Press &lt;code&gt;Ctrl+C&lt;/code&gt; in both terminals:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The terminal running Uvicorn.&lt;/li&gt;
&lt;li&gt;The terminal running the Python HTTP server.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>webdev</category>
      <category>python</category>
      <category>security</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
