DEV Community

xbill for Google Developer Experts

Posted on • Originally published at xbill999.Medium on

Building a Secure MCP Server with Python, Cloud Run, and Gemini CLI

Leveraging Gemini CLI and the underlying Gemini LLM to build secure Model Context Protocol (MCP) AI applications in the Python programming language with a local development environment.

Aren’t There a Billion Python MCP Demos?

Yes there are.

Python has traditionally been the main coding language for ML and AI tools. The goal of this article is to provide a minimal viable basic working MCP stdio server that can be run locally without any unneeded extra code or extensions.

What Is Python?

Python is an interpreted language that allows for rapid development and testing and has deep libraries for working with ML and AI:

Welcome to Python.org

Python Version Management

One of the downsides of the wide deployment of Python has been managing the language versions across platforms and maintaining a supported version.

The pyenv tool enables deploying consistent versions of Python:

GitHub - pyenv/pyenv: Simple Python version management

As of writing — the mainstream python version is 3.13. To validate your current Python:

xbill@penguin:~$ python --version
Python 3.13.12

xbill@penguin:~$ pyenv version
3.13.12 (set by /home/xbill/.pyenv/version)
Enter fullscreen mode Exit fullscreen mode

Gemini CLI

If not pre-installed you can download the Gemini CLI to interact with the source files and provide real-time assistance:

npm install -g @google/gemini-cli
Enter fullscreen mode Exit fullscreen mode

Testing the Gemini CLI Environment

Once you have all the tools and the correct Node.js version in place- you can test the startup of Gemini CLI. You will need to authenticate with a Key or your Google Account:

gemini
Enter fullscreen mode Exit fullscreen mode

Node Version Management

Gemini CLI needs a consistent, up to date version of Node. The nvm command can be used to get a standard Node environment:

GitHub - nvm-sh/nvm: Node Version Manager - POSIX-compliant bash script to manage multiple active node.js versions

Python MCP Documentation

The official GitHub Repo provides samples and documentation for getting started:

GitHub - modelcontextprotocol/python-sdk: The official Python SDK for Model Context Protocol servers and clients

Where do I start?

The strategy for starting MCP development is a incremental step by step approach.

First, the basic development environment is setup with the required system variables, and a working Gemini CLI configuration.

Then, a minimal Hello World Style Python MCP Server is built with HTTP transport. This server is validated with Gemini CLI in the local environment.

This setup validates the connection from Gemini CLI to the local server via MCP. The MCP client (Gemini CLI) and the Python MCP server both run in the same local environment.

Finally — the entire solution is deployed to Google Cloud Run.

Setup the Basic Environment

At this point you should have a working Python environment and a working Gemini CLI installation. The next step is to clone the GitHub samples repository with support scripts:

cd ~
git clone https://github.com/xbill9/iap-https-rust
Enter fullscreen mode Exit fullscreen mode

Then run init.sh from the cloned directory.

The script will attempt to determine your shell environment and set the correct variables:

cd iap-https-rust
source init.sh
Enter fullscreen mode Exit fullscreen mode

If your session times out or you need to re-authenticate- you can run the set_env.sh script to reset your environment variables:

cd iap-https-rust
source set_env.sh
Enter fullscreen mode Exit fullscreen mode

Variables like PROJECT_ID need to be setup for use in the various build scripts- so the set_env script can be used to reset the environment if you time-out.

Python Info Tool with HTTP Transport

One of the key features that the standard MCP libraries provide is abstracting various transport methods.

The high level MCP tool implementation is the same no matter what low level transport channel/method that the MCP Client uses to connect to a MCP Server.

The simplest transport that the SDK supports is the stdio (stdio/stdout) transport — which connects a locally running process. Both the MCP client and MCP Server must be running in the same environment.

The HTTP transport allows the MCP client and server to run on the same system or be distributed over the Internet.

The connection over HTTP will look similar to this:

transport = os.environ.get("MCP_TRANSPORT", "sse")

    logger.info(f"Starting httpkey-python MCP server (Transport: {transport})")

    if transport == "sse":
        mcp.run(transport="sse")
Enter fullscreen mode Exit fullscreen mode

Running the Python Code

First- switch the directory with the Python version of the MCP sample code:

cd ~/iap-https-rust/manual-python
Enter fullscreen mode Exit fullscreen mode

Run the release version on the local system:

xbill@penguin:~/iap-https-rust/manual-python$ make
Processing ./.
  Installing build dependencies ... done
  Getting requirements to build wheel ... done
  Preparing metadata (pyproject.toml) ... done
Enter fullscreen mode Exit fullscreen mode

You can validate the final result by checking the messages:

Successfully built httpkey-python
Installing collected packages: httpkey-python
  Attempting uninstall: httpkey-python
    Found existing installation: httpkey-python 0.5.0
    Uninstalling httpkey-python-0.5.0:
      Successfully uninstalled httpkey-python-0.5.0
Successfully installed httpkey-python-0.5.0
Enter fullscreen mode Exit fullscreen mode

The project can also be linted:

xbill@penguin:~/iap-https-rust/manual-python$ make lint
Requirement already satisfied: ruff in /home/xbill/.pyenv/versions/3.13.12/lib/python3.13/site-packages (0.15.0)
All checks passed!
Enter fullscreen mode Exit fullscreen mode

And a test run:

xbill@penguin:~/iap-https-rust/manual-python$ make test
Running tests...
.....2026-02-10 20:09:18,746 - httpkey-python - INFO - Fetching MCP API Key for project: test-project
2026-02-10 20:09:18,746 - httpkey-python - INFO - Falling back to library-based API key fetch
.2026-02-10 20:09:18,747 - httpkey-python - INFO - Fetching MCP API Key for project: test-project
2026-02-10 20:09:18,748 - httpkey-python - INFO - Successfully fetched API key via gcloud
.../home/xbill/.local/lib/python3.13/site-packages/starlette/applications.py:233: DeprecationWarning: The `middleware` decorator is deprecated, and will be removed in version 1.0.0. Refer to https://starlette.dev/middleware/#using-middleware for recommended approach.
  warnings.warn(
2026-02-10 20:09:18,761 - httpx - INFO - HTTP Request: GET http://testserver/sse "HTTP/1.1 401 Unauthorized"
2026-02-10 20:09:18,763 - httpx - INFO - HTTP Request: GET http://testserver/sse "HTTP/1.1 401 Unauthorized"
Enter fullscreen mode Exit fullscreen mode

Running the Tool Locally

Once the release version has been built- the resulting binary can be executed directly in the local environment.

The quick summary of local system info can be run right from the Makefile:

xbill@penguin:~/iap-https-rust/manual-python$ make info
MCP API Key Status
------------------
Provided Key: [NOT FOUND]

Authentication Failed: Invalid or missing API Key
make: *** [Makefile:30: info] Error 1
Enter fullscreen mode Exit fullscreen mode

This call failed because no API key was provided on the command line or in the current environment.

The tool will also fail if an invalid key is passed:

xbill@penguin:~/iap-https-rust/manual-python$ export MCP_API_KEY=2snakes
xbill@penguin:~/iap-https-rust/manual-python$ make info
MCP API Key Status
------------------
Provided Key: [NOT FOUND]

Authentication Failed: Invalid or missing API Key
make: *** [Makefile:30: info] Error 1
Enter fullscreen mode Exit fullscreen mode

Setting an API Key

On project setup the init.sh script configures the Google Cloud environment and creates a sample key to secure the connection. To set this key in the current environment — use the set_key.sh script:

xbill@penguin:~/iap-https-rust/manual-python$ source ../set_key.sh 
--- Setting Google Cloud Project ID ---
Using Google Cloud project: comglitn
Checking for existing MCP API Key...
Using existing MCP API Key: projects/1056842563084/locations/global/keys/cbd6422f-e594-4536-9ad9-6f179f43f11b
Retrieving API Key string...
MCP API Key retrieved and exported.

This key can be used with all variants that support API key validation:
  - Rust: manual, local, stdiokey
  - Python: manual-python, local-python, stdiokey-python

Ensure this script was sourced: source ./set_key.sh
--- Environment Checks ---
Not running in Google Cloud VM or Shell. Checking ADC...
Running on ChromeOS.
--- Initial Setup complete ---
Enter fullscreen mode Exit fullscreen mode

The tool can now execute:

xbill@penguin:~/iap-https-rust/manual-python$ make info KEY=$MCP_API_KEY
2026-02-10 20:14:41,473 - httpkey-python - INFO - Fetching MCP API Key for project: comglitn
2026-02-10 20:14:42,949 - httpkey-python - INFO - Successfully fetched API key via gcloud
System Information Report
=========================

MCP API Key Status
------------------
Provided Key: [FOUND]
Cloud Match: [MATCHED]
Enter fullscreen mode Exit fullscreen mode

System Information with MCP HTTP Transport

One of the key features that the MCP protocol provides is abstracting various transport methods.

The high level tool MCP implementation is the same no matter what low level transport channel/method that the MCP Client uses to connect to a MCP Server.

The simplest transport that the SDK supports is the stdio (stdio/stdout) transport — which connects a locally running process. Both the MCP client and MCP Server must be running in the same environment.

The HTTP transport allows the MCP client and server to run in the same environment or be distributed over the Internet.

First- switch the directory with the HTTP sample code:

xbill@penguin:~/iap-https-rust/manual-python$ make run
Running the MCP HTTP (SSE) server...
2026-02-10 20:15:51,053 - httpkey-python - INFO - Fetching MCP API Key for project: comglitn
2026-02-10 20:15:52,451 - httpkey-python - INFO - Successfully fetched API key via gcloud
2026-02-10 20:15:52,451 - httpkey-python - INFO - Successfully fetched MCP API Key from Google Cloud settings
2026-02-10 20:15:52,452 - httpkey-python - INFO - Starting httpkey-python MCP server (Transport: sse)
2026-02-10 20:15:52,452 - httpkey-python - INFO - API Key verification enabled (X-Goog-Api-Key)
INFO: Started server process [26387]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8080 (Press CTRL+C to quit)
Enter fullscreen mode Exit fullscreen mode

This step validates that the tool can be started locally and uses the MCP_API_KEY in the local environment.

Deploying to Cloud Run

After the HTTP version of the MCP server has been tested locally — it can be deployed remotely to Google Cloud Run.

First- switch to the directory with the HTTP MCP sample code:

xbill@penguin:~/iap-https-rust/manual-python$ 
Enter fullscreen mode Exit fullscreen mode

Deploy the project to Google Cloud Run with the pre-built cloudbuild.yaml and Dockerfile:

xbill@penguin:~/iap-https-rust/manual-python$ make deploy
Deploying to Google Cloud Run...
Creating temporary archive of 11 file(s) totalling 28.6 KiB before compression.
Some files were not included in the source upload.

Check the gcloud log [/home/xbill/.config/gcloud/logs/2026.02.10/20.17.04.996502.log] to see which files and the contents of the
default gcloudignore file used (see `$ gcloud topic gcloudignore` to learn
more).

Uploading tarball of [.] to [gs://comglitn_cloudbuild/source/1770772625.175254-eff3411dce1040658895763e847ba739.tgz]
Created [https://cloudbuild.googleapis.com/v1/projects/comglitn/locations/global/builds/01c5df5f-ce99-4084-9f14-aa62bada354b].
Logs are available at [https://console.cloud.google.com/cloud-build/builds/01c5df5f-ce99-4084-9f14-aa62bada354b?project=1056842563084].
Waiting for build to complete. Polling interval: 1 second(s).
Enter fullscreen mode Exit fullscreen mode

The Cloud Build will start:

Operation completed over 1 objects/9.2 KiB.
BUILD
Starting Step #0
Step #0: Already have image (with digest): gcr.io/cloud-builders/docker
Step #0: Sending build context to Docker daemon 39.42kB
Step #0: Step 1/6 : FROM python:3.12-slim
Step #0: 3.12-slim: Pulling from library/python
Step #0: 0c8d55a45c0d: Already exists
Enter fullscreen mode Exit fullscreen mode

It can take 15–30 minutes to complete the build.

The cloud build needs to pull in all the Python libraries in the build environment and generate the entire package from scratch:

Starting Step #1
Step #1: Already have image (with digest): gcr.io/cloud-builders/gcloud
Step #1: Deploying container to Cloud Run service [sysutils-manual-python] in project [comglitn] region [us-central1]
Step #1: Deploying...
Step #1: Setting IAM Policy..........done
Step #1: Creating Revision.................................................................................................................................................................................................................................................................................................................................................................................done
Step #1: Routing traffic.....done
Step #1: Done.
Step #1: Service [sysutils-manual-python] revision [sysutils-manual-python-00003-4n5] has been deployed and is serving 100 percent of traffic.
Step #1: Service URL: https://sysutils-manual-python-1056842563084.us-central1.run.app
Finished Step #1
PUSH
Enter fullscreen mode Exit fullscreen mode

When the build is complete- an endpoint will be returned. The service endpoint in this example is :

https://sysutils-manual-python-1056842563084.us-central1.run.app
Enter fullscreen mode Exit fullscreen mode

The actual endpoint will vary based on your project settings.

Review Service in Cloud Run

Navigate to the Google Cloud console and search for Cloud Run -

and then you can detailed information on the Cloud Run Service:

Cloud Logging

The remote server writes logs to stderr in standard JSON format. These logs are available from the deployed Cloud Run Service:

Validate HTTP connection

Once you have the Endpoint — you can attempt a connection- navigate to in your browser:

https://sysutils-manual-python-1056842563084.us-central1.run.app/
Enter fullscreen mode Exit fullscreen mode

You will need to adjust the exact URL to match the URL returned from Cloud Build.

You will get an error- this connection is expecting a message in the MCP format:

{"error":"Unauthorized: Invalid or missing API Key"}
Enter fullscreen mode Exit fullscreen mode

Pass the API Key in Gemini Settings

The stdio server checks the API key if it is provided. The set_key.sh scripts sets the environment variable from the Google Cloud settings. A sample Gemini setup is provided for this scenario as well:

{
  "mcpServers": {
    "cloudrun-manual-python": {
             "url": "https://sysutils-manual-python-$PROJECT_NUMBER.us-central1.run.app/sse",
       "headers": {
         "X-Goog-Api-Key": "$MCP_API_KEY"
       }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The set_key.sh will set the required environment variables:

xbill@penguin:~/iap-https-rust/manual-python$ source ../set_key.sh 
--- Setting Google Cloud Project ID ---
Using Google Cloud project: comglitn
Checking for existing MCP API Key...
Using existing MCP API Key: projects/1056842563084/locations/global/keys/cbd6422f-e594-4536-9ad9-6f179f43f11b
Retrieving API Key string...
MCP API Key retrieved and exported.

This key can be used with all variants that support API key validation:
  - Rust: manual, local, stdiokey
  - Python: manual-python, local-python, stdiokey-python

Ensure this script was sourced: source ./set_key.sh
--- Environment Checks ---
Not running in Google Cloud VM or Shell. Checking ADC...
Running on ChromeOS.
--- Initial Setup complete ---
Enter fullscreen mode Exit fullscreen mode

Next Gemini CLI is used to check the MCP connection settings:

 > /mcp list
Configured MCP servers:

🟢 cloudrun-manual-python - Ready (2 tools)
  Tools:
  - disk_usage
  - local_system_info
Enter fullscreen mode Exit fullscreen mode

The MCP Server can now be used directly using Gemini CLI as a MCP client. This is the same Rust binary that was tested locally as a standalone build:

> run mcp tool local_system_info
✦ I will execute the local_system_info tool to provide the requested system information report.

╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Action Required │
│ │
│ ? local_system_info (cloudrun-manual-python MCP Server) {} │
│ │
│ MCP Server: cloudrun-manual-python │
│ Tool: local_system_info │
│ Allow execution of MCP tool "local_system_info" from server "cloudrun-manual-python"? │
│ │
│ 1. Allow once │
│ 2. Allow tool for this session │
│ 3. Allow all server tools for this session │
│ ● 4. Allow tool for all future sessions │
│ 5. No, suggest changes (esc) │
│ │
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯

✦ 
    1 System Information Report
    2 =========================
    3
    4 Authentication: [VERIFIED] (Running as MCP Server)
    5
    6 System Information
    7 ------------------
    8 System Name: posix
    9 OS Name: linux
   10 Host Name: localhost
   11
   12 CPU Information
   13 ---------------
   14 Number of Cores: 2
   15
   16 Memory Information
   17 ------------------
   18 Total Memory: 1024 MB
   19 Used Memory: 91 MB
   20 Total Swap: 0 MB
   21 Used Swap: 0 MB
   22
   23 Network Interfaces
   24 ------------------
   25 lo : RX: 0 bytes, TX: 0 bytes (MAC: 00:00:00:00:00:00)
   26 eth1 : RX: 15004 bytes, TX: 7983 bytes (MAC: 00:00:00:00:00:00)
   27 eth2 : RX: 16994 bytes, TX: 11934 bytes (MAC: 42:00:4e:49:43:00)
Enter fullscreen mode Exit fullscreen mode

Validate with Invalid Key

To verify that that Cloud Run Service checks the key- call the endpoint with an invalid MCP_API_KEY:

xbill@penguin:~/iap-https-rust/manual-python$ export MCP_API_KEY=2snakes
xbill@penguin:~/iap-https-rust/manual-python$ 

ℹ MCP server 'cloudrun-manual-python' requires authentication using: /mcp auth cloudrun-manual-python
 > /mcp list
Configured MCP servers:

🔴 cloudrun-manual-python - Disconnected (OAuth not authenticated)
Enter fullscreen mode Exit fullscreen mode

Summary

The strategy for using Python for MCP development with Gemini CLI was validated with a incremental step by step approach.

A minimal HTTP transport MCP Server was started from Python source code and validated with Gemini CLI running as a MCP client in the same local environment.

This basic Python code was enhanced with an API key generated and stored in the main Google Cloud project.

This API key was used to validate access to the local Python tool and the HTTP MCP version directly from Gemini CLI.

This approach can be extended to more complex deployments using other MCP transports and Cloud based options.

Top comments (0)