<?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: Timothy Olaleke</title>
    <description>The latest articles on DEV Community by Timothy Olaleke (@timtech4u).</description>
    <link>https://dev.to/timtech4u</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F33660%2F42e1a769-747b-4365-b480-c3147f79a656.jpeg</url>
      <title>DEV Community: Timothy Olaleke</title>
      <link>https://dev.to/timtech4u</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/timtech4u"/>
    <language>en</language>
    <item>
      <title>Your AI Coding Agent Deserves Its Own Cloud Machine</title>
      <dc:creator>Timothy Olaleke</dc:creator>
      <pubDate>Wed, 01 Apr 2026 11:24:07 +0000</pubDate>
      <link>https://dev.to/timtech4u/your-ai-coding-agent-deserves-its-own-cloud-machine-1ino</link>
      <guid>https://dev.to/timtech4u/your-ai-coding-agent-deserves-its-own-cloud-machine-1ino</guid>
      <description>&lt;p&gt;I've been running Claude Code locally for months. It works, but my laptop fan sounds like a jet engine, my battery drains in two hours, and if I close the lid mid-session — gone.&lt;/p&gt;

&lt;p&gt;Last week I moved my entire dev environment to a cloud VM. Claude Code runs there, my full stack runs there, and I SSH tunnel everything back to localhost. From my laptop it feels identical. From my phone, I can check on my AI coding agent while grabbing coffee.&lt;/p&gt;

&lt;p&gt;Here's the exact setup. Every command is copy-pasteable. I verified each step end-to-end.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 0: Get Free Google Cloud Credits
&lt;/h2&gt;

&lt;p&gt;If you don't have a GCP account yet, you get &lt;strong&gt;$300 in free credits&lt;/strong&gt; for 90 days — no charge until you explicitly upgrade. That's more than enough to run this setup for months.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://cloud.google.com/free" rel="noopener noreferrer"&gt;cloud.google.com/free&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Get started for free&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Sign in with your Google account, add a payment method (you won't be charged)&lt;/li&gt;
&lt;li&gt;You now have $300 in credits&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Already have an account? Check your remaining credits at &lt;a href="https://console.cloud.google.com/billing" rel="noopener noreferrer"&gt;Billing → Overview&lt;/a&gt; in the console.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What this setup costs:&lt;/strong&gt; about $0.13/hour for the VM. With the free tier, you can run it ~2,300 hours — roughly 3 months of 24/7 usage, or 6+ months during work hours only.&lt;/p&gt;

&lt;p&gt;If you're a startup, apply for the &lt;a href="https://cloud.google.com/startup" rel="noopener noreferrer"&gt;Google for Startups Cloud Program&lt;/a&gt; — up to $200k in credits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a Remote Dev Machine?
&lt;/h2&gt;

&lt;p&gt;Three reasons pushed me off localhost:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Battery and thermals&lt;/strong&gt; — AI coding agents hammer your CPU. A cloud VM doesn't care.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session persistence&lt;/strong&gt; — Close your laptop, hop on your phone, come back to your desk. The session is still running.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dev environment consistency&lt;/strong&gt; — Docker, databases, everything runs 24/7 on the VM. No more "let me spin up Postgres first."&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The tradeoff is latency. SSH tunneling adds a few milliseconds. I can't feel it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────┐     SSH Tunnel      ┌──────────────────────┐
│   Laptop    │ ──────────────────&amp;gt; │     Cloud VM          │
│  localhost  │  port forwarding    │  ├── Your App         │
└─────────────┘                     │  ├── API Server       │
                                    │  ├── PostgreSQL       │
┌─────────────┐     Happy (E2E)     │  ├── Redis            │
│   Phone     │ ──────────────────&amp;gt; │  └── Claude Code      │
│  Happy App  │  encrypted sync     │     (in tmux)         │
└─────────────┘                     └──────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your laptop and phone are thin clients. The VM does all the work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Create the VM
&lt;/h2&gt;

&lt;p&gt;I'm using Google Cloud because my Claude Code runs on Vertex AI (covered that in &lt;a href="https://dev.to/timtech4u/run-claude-code-on-google-cloud-use-your-gcp-credits-for-ai-coding-desktop-control-and-more-2151"&gt;my previous post&lt;/a&gt;). But this works with any cloud provider.&lt;/p&gt;

&lt;p&gt;First, make sure you have the &lt;code&gt;gcloud&lt;/code&gt; CLI installed. If not:&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="c"&gt;# macOS&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;google-cloud-sdk

&lt;span class="c"&gt;# Or download from https://cloud.google.com/sdk/docs/install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then authenticate and set your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud auth login
gcloud config &lt;span class="nb"&gt;set &lt;/span&gt;project YOUR_PROJECT_ID
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the VM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud compute instances create my-dev-vm &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;us-central1-a &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--machine-type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;e2-standard-4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--boot-disk-size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;50GB &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--boot-disk-type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pd-ssd &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image-family&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ubuntu-2404-lts-amd64 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image-project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ubuntu-os-cloud
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's 4 vCPUs, 16GB RAM, 50GB SSD. Costs about $0.13/hour — covered by your free credits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Install Everything on the VM
&lt;/h2&gt;

&lt;p&gt;SSH in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud compute ssh my-dev-vm &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;us-central1-a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run these on the VM:&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="c"&gt;# Docker&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://get.docker.com | sh
&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; docker &lt;span class="nv"&gt;$USER&lt;/span&gt;
newgrp docker

&lt;span class="c"&gt;# Node.js 22 + pnpm&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://deb.nodesource.com/setup_22.x | &lt;span class="nb"&gt;sudo &lt;/span&gt;bash -
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; nodejs
&lt;span class="nb"&gt;sudo &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; pnpm@9

&lt;span class="c"&gt;# Claude Code&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @anthropic-ai/claude-code

&lt;span class="c"&gt;# Happy (mobile access to Claude Code)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; happy

&lt;span class="c"&gt;# tmux (persistent sessions)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; tmux
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify everything installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nt"&gt;--version&lt;/span&gt;    &lt;span class="c"&gt;# Docker 29+&lt;/span&gt;
node &lt;span class="nt"&gt;--version&lt;/span&gt;      &lt;span class="c"&gt;# v22+&lt;/span&gt;
claude &lt;span class="nt"&gt;--version&lt;/span&gt;    &lt;span class="c"&gt;# Claude Code 2.x&lt;/span&gt;
tmux &lt;span class="nt"&gt;-V&lt;/span&gt;             &lt;span class="c"&gt;# tmux 3.x&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Clone Your Project and Start Services
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/your-org/your-project.git
&lt;span class="nb"&gt;cd &lt;/span&gt;your-project
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify everything 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;docker compose ps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see your containers up with status &lt;code&gt;(healthy)&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;NAME             STATUS              PORTS
&lt;/span&gt;&lt;span class="gp"&gt;my-app-api       Up 3 min (healthy)  0.0.0.0:3000-&amp;gt;&lt;/span&gt;3000/tcp
&lt;span class="gp"&gt;my-app-web       Up 3 min (healthy)  0.0.0.0:3100-&amp;gt;&lt;/span&gt;3100/tcp
&lt;span class="gp"&gt;my-app-postgres  Up 5 min (healthy)  0.0.0.0:5432-&amp;gt;&lt;/span&gt;5432/tcp
&lt;span class="gp"&gt;my-app-redis     Up 5 min (healthy)  0.0.0.0:6379-&amp;gt;&lt;/span&gt;6379/tcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your full stack is running on the VM. Your laptop isn't doing anything yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: SSH Tunnel to Localhost
&lt;/h2&gt;

&lt;p&gt;This is the key part. From your &lt;strong&gt;local machine&lt;/strong&gt; (not the VM), forward the ports you need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud compute ssh my-dev-vm &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;us-central1-a &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-L&lt;/span&gt; 3100:localhost:3100 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-L&lt;/span&gt; 3000:localhost:3000 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-N&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What this does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-L 3100:localhost:3100&lt;/code&gt; — requests to &lt;code&gt;localhost:3100&lt;/code&gt; on your laptop go to port 3100 on the VM&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-N&lt;/code&gt; — don't open a shell, just forward ports&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now open your browser:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;http://localhost:3100&lt;/code&gt; — your web app&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;http://localhost:3000&lt;/code&gt; — your API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's localhost. Your browser doesn't know the difference. Hot reload works. DevTools work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Save it as an alias&lt;/strong&gt; (add to &lt;code&gt;~/.zshrc&lt;/code&gt; or &lt;code&gt;~/.bashrc&lt;/code&gt; on your local machine):&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;alias &lt;/span&gt;dev-tunnel&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'gcloud compute ssh my-dev-vm --zone=us-central1-a -- \
  -L 3100:localhost:3100 -L 3000:localhost:3000 -N'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5: Run Claude Code in tmux
&lt;/h2&gt;

&lt;p&gt;SSH back into the VM and start Claude Code in a tmux session so it survives disconnects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud compute ssh my-dev-vm &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;us-central1-a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the VM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tmux new &lt;span class="nt"&gt;-s&lt;/span&gt; claude
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/your-project
claude
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;If you're using Vertex AI&lt;/strong&gt; (GCP credits for Claude), add these to &lt;code&gt;~/.bashrc&lt;/code&gt; on the VM first:&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;CLAUDE_CODE_USE_VERTEX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_VERTEX_PROJECT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YOUR_PROJECT_ID
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CLOUD_ML_REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;global
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;claude-opus-4-6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then &lt;code&gt;source ~/.bashrc&lt;/code&gt; before running &lt;code&gt;claude&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Claude Code now has your full project right there — files, Docker services, databases, everything. No latency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;tmux basics:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Ctrl+b d&lt;/code&gt; — detach (session keeps running in the background)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tmux attach -t claude&lt;/code&gt; — reattach from any SSH session&lt;/li&gt;
&lt;li&gt;Close your laptop, come back tomorrow — session is exactly where you left it&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 6: Code from Your Phone with Happy
&lt;/h2&gt;

&lt;p&gt;This changed my workflow. &lt;a href="https://github.com/slopus/happy" rel="noopener noreferrer"&gt;Happy&lt;/a&gt; is an open-source mobile client for Claude Code. End-to-end encrypted.&lt;/p&gt;

&lt;p&gt;On the VM, in a new tmux session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tmux new &lt;span class="nt"&gt;-s&lt;/span&gt; happy
happy claude &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--dangerously-skip-permissions&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--dangerously-skip-permissions&lt;/code&gt; flag runs Claude Code in "yolo mode" — no permission prompts. Remove it if you want the default safety checks.&lt;/p&gt;

&lt;p&gt;Happy shows a QR code. Scan it with the Happy app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://apps.apple.com/us/app/happy-claude-code-client/id6748571505" rel="noopener noreferrer"&gt;iOS App Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://play.google.com/store/apps/details?id=com.ex3ndr.happy" rel="noopener noreferrer"&gt;Google Play Store&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. You control Claude Code from your phone. When you get back to your desk, press any key on your keyboard and control switches back.&lt;/p&gt;

&lt;p&gt;I use this constantly. Standing in line? Check if tests passed. On a call? Glance at what Claude is building.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Daily Workflow
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Morning:&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;dev-tunnel  &lt;span class="c"&gt;# alias from Step 4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;localhost:3100&lt;/code&gt;. Everything is already running from yesterday. No cold start.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;During the day:&lt;/strong&gt;&lt;br&gt;
Claude Code runs in tmux on the VM. Attach when I need it, detach when I don't. Laptop stays cool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Away from desk:&lt;/strong&gt;&lt;br&gt;
Happy app on my phone. "Run the test suite." "Show me the API logs."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;End of day:&lt;/strong&gt;&lt;br&gt;
Close the laptop. VM keeps running. Tomorrow: &lt;code&gt;dev-tunnel&lt;/code&gt; and I'm back in 2 seconds.&lt;/p&gt;
&lt;h2&gt;
  
  
  Cost
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;Spec&lt;/th&gt;
&lt;th&gt;Monthly&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;VM (e2-standard-4)&lt;/td&gt;
&lt;td&gt;4 vCPU, 16GB RAM&lt;/td&gt;
&lt;td&gt;~$97 (or ~$39 spot)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disk (50GB SSD)&lt;/td&gt;
&lt;td&gt;pd-ssd&lt;/td&gt;
&lt;td&gt;~$8.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network&lt;/td&gt;
&lt;td&gt;SSH tunnel overhead&lt;/td&gt;
&lt;td&gt;~$1-2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;~$107&lt;/strong&gt; (or &lt;strong&gt;~$49 spot&lt;/strong&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All covered by the $300 free credits for about 3 months.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auto-stop to save money:&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;gcloud compute resource-policies create vm-maintenance workday-schedule &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--start-schedule&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"0 8 * * 1-5"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--stop-schedule&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"0 20 * * 1-5"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--timezone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;UTC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Tips
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use &lt;code&gt;autossh&lt;/code&gt; for reliable tunnels&lt;/strong&gt; — SSH tunnels drop. &lt;code&gt;autossh&lt;/code&gt; reconnects:&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="c"&gt;# Install: brew install autossh (macOS) or apt install autossh (Linux)&lt;/span&gt;
autossh &lt;span class="nt"&gt;-M&lt;/span&gt; 0 &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nt"&gt;-N&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"ServerAliveInterval 30"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"ServerAliveCountMax 3"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-L&lt;/span&gt; 3100:localhost:3100 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-L&lt;/span&gt; 3000:localhost:3000 &lt;span class="se"&gt;\&lt;/span&gt;
  my-dev-vm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;VS Code Remote SSH&lt;/strong&gt; — connect your IDE directly to the VM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cmd+Shift+P → "Remote-SSH: Connect to Host" → my-dev-vm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Multiple projects on one VM:&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="nb"&gt;cd&lt;/span&gt; ~/project-a &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker compose &lt;span class="nt"&gt;-p&lt;/span&gt; project-a up &lt;span class="nt"&gt;-d&lt;/span&gt;  &lt;span class="c"&gt;# ports 3000-3100&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/project-b &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker compose &lt;span class="nt"&gt;-p&lt;/span&gt; project-b up &lt;span class="nt"&gt;-d&lt;/span&gt;  &lt;span class="c"&gt;# ports 4000-4100&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Quick Reference
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Get free credits&lt;/td&gt;
&lt;td&gt;&lt;a href="https://cloud.google.com/free" rel="noopener noreferrer"&gt;cloud.google.com/free&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Create VM&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gcloud compute instances create my-dev-vm --machine-type=e2-standard-4 --boot-disk-size=50GB ...&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSH in&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gcloud compute ssh my-dev-vm&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Start stack&lt;/td&gt;
&lt;td&gt;&lt;code&gt;docker compose up -d&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tunnel ports&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gcloud compute ssh my-dev-vm -- -L 3100:localhost:3100 -L 3000:localhost:3000 -N&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;tmux new -s claude&lt;/code&gt; then &lt;code&gt;claude&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mobile access&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;happy claude&lt;/code&gt; → scan QR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stop VM&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gcloud compute instances stop my-dev-vm&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Start VM&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gcloud compute instances start my-dev-vm&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Your laptop should be a thin client. Let the cloud do the heavy lifting.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Part 2 of my Claude Code on Google Cloud series. Part 1: &lt;a href="https://dev.to/timtech4u/run-claude-code-on-google-cloud-use-your-gcp-credits-for-ai-coding-desktop-control-and-more-2151"&gt;Run Claude Code on Google Cloud&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cloudcomputing</category>
      <category>ai</category>
      <category>devops</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Run Claude Code on Google Cloud: Use Your GCP Credits for AI Coding, Desktop Control, and More</title>
      <dc:creator>Timothy Olaleke</dc:creator>
      <pubDate>Tue, 31 Mar 2026 10:14:45 +0000</pubDate>
      <link>https://dev.to/timtech4u/run-claude-code-on-google-cloud-use-your-gcp-credits-for-ai-coding-desktop-control-and-more-2151</link>
      <guid>https://dev.to/timtech4u/run-claude-code-on-google-cloud-use-your-gcp-credits-for-ai-coding-desktop-control-and-more-2151</guid>
      <description>&lt;h1&gt;
  
  
  Run Claude Code on Google Cloud: Use Your GCP Credits for AI Coding, Desktop Control, and More
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Claude Code runs natively on Google Cloud's Vertex AI.&lt;/strong&gt; Same models, same capabilities (coding, agents, MCP servers, even desktop control) but billed entirely through your GCP account.&lt;/p&gt;

&lt;p&gt;If you're already on Google Cloud, you don't need a separate Anthropic subscription. Your existing credits, billing, and IAM setup just work. Here's exactly how to set it up.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Get
&lt;/h2&gt;

&lt;p&gt;This isn't a watered-down version. Through Vertex AI, you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;All Claude models&lt;/strong&gt;: Opus 4.6, Sonnet 4.6, Haiku 4.5, and previous generations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full Claude Code features&lt;/strong&gt;: file editing, code generation, terminal commands, MCP servers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Desktop control&lt;/strong&gt;: screenshots, mouse clicks, keyboard input, browser automation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your existing GCP billing&lt;/strong&gt;: startup credits, free trial, enterprise spend, committed use discounts&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Anthropic Direct&lt;/th&gt;
&lt;th&gt;Vertex AI&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Billing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Separate Anthropic account&lt;/td&gt;
&lt;td&gt;Your existing GCP billing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Credits&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Pay as you go&lt;/td&gt;
&lt;td&gt;Use GCP/startup credits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Auth&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;API key management&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;gcloud auth&lt;/code&gt; (already configured)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Compliance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Separate vendor&lt;/td&gt;
&lt;td&gt;Same GCP governance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Models&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;All Claude models&lt;/td&gt;
&lt;td&gt;All Claude models (same day availability)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Desktop Control&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Built-in (requires Pro/Max)&lt;/td&gt;
&lt;td&gt;Native macOS tools + MCP servers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Step 1: Enable Claude in the Model Garden
&lt;/h2&gt;

&lt;p&gt;Go to &lt;a href="https://console.cloud.google.com/vertex-ai/model-garden" rel="noopener noreferrer"&gt;Vertex AI Model Garden&lt;/a&gt; in your GCP console. Filter by &lt;strong&gt;Anthropic&lt;/strong&gt; under Providers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foo8d0a9bee1grahho0cy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foo8d0a9bee1grahho0cy.png" alt="Google Cloud Console showing Vertex AI Model Garden filtered by Anthropic provider with 8 Claude models including Sonnet 4.6, Opus 4.6, Opus 4.5, and Haiku 4.5" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll see &lt;strong&gt;8 Claude models&lt;/strong&gt; available:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Claude Opus 4.6&lt;/strong&gt;: Most intelligent, industry leader for coding and agents&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude Sonnet 4.6&lt;/strong&gt;: Frontier intelligence for high volume coding and research&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude Haiku 4.5&lt;/strong&gt;: Fastest and most cost effective&lt;/li&gt;
&lt;li&gt;Plus previous generations (Opus 4.5, 4.1, 4, Sonnet 4)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Click any model to see its details and model ID:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7apkxc42d0m6klxswqce.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7apkxc42d0m6klxswqce.png" alt="Claude Opus 4.6 detail page on Vertex AI showing model ID, Open in Vertex AI Studio button, 1M token input limit, and supported data types" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;"Open in Vertex AI Studio"&lt;/strong&gt; to test the model directly in the console before setting up Claude Code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Configure Claude Code
&lt;/h2&gt;

&lt;p&gt;Install Claude Code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @anthropic-ai/claude-code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add three environment variables to your &lt;code&gt;~/.zshrc&lt;/code&gt; or &lt;code&gt;~/.bashrc&lt;/code&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="c"&gt;# Route Claude Code through Vertex AI&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CLAUDE_CODE_USE_VERTEX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1

&lt;span class="c"&gt;# Your GCP project ID (find it at console.cloud.google.com)&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_VERTEX_PROJECT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my-gcp-project

&lt;span class="c"&gt;# Region (us-east5, europe-west1, or global)&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CLOUD_ML_REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;us-east5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No API keys to manage. Claude Code picks up your existing &lt;code&gt;gcloud&lt;/code&gt; authentication automatically.&lt;/p&gt;

&lt;p&gt;Make sure you're authenticated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud auth login
gcloud auth application-default login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Want to use Opus instead of Sonnet (the default)?&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;ANTHROPIC_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;claude-opus-4-6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Run It
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;source&lt;/span&gt; ~/.zshrc
claude
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything works exactly the same: file editing, code generation, terminal commands, MCP servers. The only difference? Billing goes to your GCP account.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Desktop Control on Vertex AI
&lt;/h2&gt;

&lt;p&gt;Here's where it gets interesting. Claude Code's built-in computer use requires a claude.ai Pro/Max subscription. But on Vertex AI, you can get the same capabilities using native macOS tools:&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="c"&gt;# Take a screenshot (Claude Code is multimodal and can read images)&lt;/span&gt;
screencapture &lt;span class="nt"&gt;-x&lt;/span&gt; /tmp/screenshot.png

&lt;span class="c"&gt;# Mouse and keyboard control&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;cliclick

&lt;span class="c"&gt;# Click at coordinates&lt;/span&gt;
cliclick c:500,300

&lt;span class="c"&gt;# Type text&lt;/span&gt;
cliclick t:&lt;span class="s2"&gt;"Hello World"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For browser automation, add an MCP server to &lt;code&gt;~/.claude/mcp.json&lt;/code&gt;:&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;"mcpServers"&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;"chrome-devtools"&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;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&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="s2"&gt;"chrome-devtools-mcp@latest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"--autoConnect"&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="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;Now Claude Code can control Chrome, navigate pages, fill forms, and take screenshots. All running through Vertex AI on your GCP credits.&lt;/p&gt;

&lt;p&gt;I used this exact setup to take the screenshots in this post. Claude Code navigated the GCP console, filtered the Model Garden by Anthropic, and captured everything automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pro Tips
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Use the &lt;code&gt;global&lt;/code&gt; Region
&lt;/h3&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;CLOUD_ML_REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;global
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;global&lt;/code&gt; endpoint gets the latest models first and avoids the 10% regional price premium on older model versions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Switch Between Vertex and Direct
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Quick alias for direct Anthropic API when needed&lt;/span&gt;
cc&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;CLAUDE_CODE_USE_VERTEX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 claude &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Check Your Quotas
&lt;/h3&gt;

&lt;p&gt;Vertex AI has per region quotas for Claude models:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Console &amp;gt; IAM &amp;amp; Admin &amp;gt; Quotas &amp;gt; Filter: "anthropic"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you hit rate limits, request a quota increase or switch regions.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;th&gt;How&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Enable Claude&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Model Garden &amp;gt; Anthropic &amp;gt; Click model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Configure&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;CLAUDE_CODE_USE_VERTEX=1&lt;/code&gt; + project ID + region&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Auth&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;gcloud auth login&lt;/code&gt; (you already did this)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Billing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Your existing GCP credits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Desktop control&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;screencapture&lt;/code&gt; + &lt;code&gt;cliclick&lt;/code&gt; + MCP servers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Models&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;All Claude models (Opus 4.6, Sonnet 4.6, Haiku 4.5)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you're on GCP, your credits are sitting there. Use them.&lt;/p&gt;

</description>
      <category>gcp</category>
      <category>ai</category>
      <category>claudecode</category>
      <category>vertexai</category>
    </item>
    <item>
      <title>Your Browser Has a Remote Control — And Nobody Told You</title>
      <dc:creator>Timothy Olaleke</dc:creator>
      <pubDate>Thu, 19 Mar 2026 16:15:50 +0000</pubDate>
      <link>https://dev.to/timtech4u/your-browser-has-a-remote-control-and-nobody-told-you-5e97</link>
      <guid>https://dev.to/timtech4u/your-browser-has-a-remote-control-and-nobody-told-you-5e97</guid>
      <description>&lt;h1&gt;
  
  
  Your Browser Has a Remote Control — And Nobody Told You
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;3 tools that let AI agents drive Chrome. I tested all three. Here's what actually happens.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;Every Chrome browser ships with a hidden feature that almost nobody talks about: a remote control API called the &lt;strong&gt;Chrome DevTools Protocol (CDP)&lt;/strong&gt;. It's the same protocol that powers Chrome DevTools — the thing you open when you press F12. But here's the part that changes everything: any program can use it. Including AI agents.&lt;/p&gt;

&lt;p&gt;I've been using CDP daily for over a year to let AI agents browse the web using my real browser — with all my logged-in sessions intact. No passwords shared. No API keys. No OAuth flows. The AI just uses my browser like I would.&lt;/p&gt;

&lt;p&gt;Three major tools have emerged to give AI agents this superpower. I tested all three on the same task, with the same browser, and discovered something that most tutorials and docs don't tell you.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Three Contenders
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Playwright MCP&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Chrome DevTools Protocol&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;agent-browser&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Made by&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Microsoft&lt;/td&gt;
&lt;td&gt;Google (built into Chrome)&lt;/td&gt;
&lt;td&gt;Vercel Labs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GitHub Stars&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;29,000+&lt;/td&gt;
&lt;td&gt;Built-in (no repo needed)&lt;/td&gt;
&lt;td&gt;23,500+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Language&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;td&gt;Any (HTTP + WebSocket)&lt;/td&gt;
&lt;td&gt;Rust&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Latest Version&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;v0.0.68&lt;/td&gt;
&lt;td&gt;Ships with Chrome&lt;/td&gt;
&lt;td&gt;v0.21.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Install&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx @playwright/mcp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Already in your browser&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npm i -g agent-browser&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;They all use CDP under the hood. But they use it very differently — and that difference matters more than you'd think.&lt;/p&gt;




&lt;h2&gt;
  
  
  Setting It Up (60 Seconds)
&lt;/h2&gt;

&lt;p&gt;Before we compare the tools, let's enable Chrome's remote control. It takes one command.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start Chrome with remote debugging:&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="c"&gt;# macOS&lt;/span&gt;
&lt;span class="s2"&gt;"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"&lt;/span&gt; &lt;span class="nt"&gt;--remote-debugging-port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;9222

&lt;span class="c"&gt;# Linux&lt;/span&gt;
google-chrome &lt;span class="nt"&gt;--remote-debugging-port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;9222

&lt;span class="c"&gt;# Windows&lt;/span&gt;
chrome.exe &lt;span class="nt"&gt;--remote-debugging-port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;9222
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Verify it's working:&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;curl http://localhost:9222/json/version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5kxuu12fny7ippec6gkk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5kxuu12fny7ippec6gkk.png" alt="One curl command confirms Chrome is ready for AI agent control" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you see a JSON response with your Chrome version, you're ready. That's the entire setup.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What just happened?&lt;/strong&gt; You told Chrome to listen on port 9222 for remote control commands. Any program on your machine can now send instructions to your browser — open tabs, read pages, click buttons, fill forms, take screenshots.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Coming soon:&lt;/strong&gt; Chrome 146+ is adding a native settings toggle for remote debugging — no command line needed. Go to &lt;strong&gt;DevTools (F12) → Settings → Experiments&lt;/strong&gt; and search for "MCP". Once enabled, AI agents can connect without restarting Chrome. This is rolling out gradually in 2025/2026.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Seeing It in Action: agent-browser
&lt;/h2&gt;

&lt;p&gt;Let's start with the most beginner-friendly tool. &lt;strong&gt;agent-browser&lt;/strong&gt; by Vercel Labs gives you 108+ simple commands to control Chrome from your terminal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install it:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; agent-browser
agent-browser &lt;span class="nb"&gt;install&lt;/span&gt;  &lt;span class="c"&gt;# Downloads a browser (first time only)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Fill a form in 4 commands:
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxu5l5fd53dnd4bkt2ik0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxu5l5fd53dnd4bkt2ik0.png" alt="agent-browser fills a pizza order form in 4 simple steps" width="800" height="551"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's what happens at each step:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1 — Open a page.&lt;/strong&gt; Just like clicking a link, but from the command line.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2 — Snapshot.&lt;/strong&gt; This is the magic for AI agents. Instead of raw HTML, you get a clean list of interactive elements with reference IDs like &lt;code&gt;[ref=e2]&lt;/code&gt;. An AI agent reads this and knows exactly what's on the page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 — Interact.&lt;/strong&gt; Use those ref IDs to fill fields, click buttons, check boxes. &lt;code&gt;agent-browser fill @e2 "John Doe"&lt;/code&gt; fills the customer name field. Simple.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4 — Screenshot.&lt;/strong&gt; Take a picture of the result. With &lt;code&gt;--annotate&lt;/code&gt;, every interactive element gets a numbered label — perfect for AI vision models.&lt;/p&gt;

&lt;h3&gt;
  
  
  Before and after:
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnu0hniwgngfdpvnz4sok.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnu0hniwgngfdpvnz4sok.png" alt="The form before: empty fields with numbered annotations showing each interactive element" width="800" height="745"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz7qhpoz0fovpwueekbue.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz7qhpoz0fovpwueekbue.png" alt="The form after: filled by agent-browser — name, phone, pizza size, toppings all selected" width="800" height="745"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The numbered red labels are &lt;code&gt;--annotate&lt;/code&gt; mode. Each number maps to an element the AI can interact with. This is how vision-based AI agents understand web pages.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Discovery: Session Sharing
&lt;/h2&gt;

&lt;p&gt;Here's the thing nobody tells you. I tested all three tools against the same page — an authenticated dashboard behind a login. Same browser, same URL, same Chrome instance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two tools saw the full dashboard.&lt;/strong&gt; One saw a login page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3pewtr018aamen18qp8a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3pewtr018aamen18qp8a.png" alt="Same browser, same URL — but different results depending on which tool you use" width="800" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What's happening?
&lt;/h3&gt;

&lt;p&gt;When you connect via &lt;strong&gt;raw CDP&lt;/strong&gt; or &lt;strong&gt;agent-browser --cdp 9222&lt;/strong&gt;, you're using Chrome's &lt;strong&gt;default browser context&lt;/strong&gt;. This means the AI agent sees exactly what you'd see — all your cookies, all your logged-in sessions, everything.&lt;/p&gt;

&lt;p&gt;When you use &lt;strong&gt;Playwright MCP&lt;/strong&gt; or &lt;strong&gt;agent-browser in standalone mode&lt;/strong&gt;, they create an &lt;strong&gt;isolated browser context&lt;/strong&gt;. Think of it like an incognito window. No cookies, no sessions, no logins. A clean slate.&lt;/p&gt;

&lt;h3&gt;
  
  
  The session sharing table:
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Sees Your Logins?&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Raw CDP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Default context&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Uses Chrome's real cookie jar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;agent-browser&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--cdp 9222&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Connects to Chrome's default context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;agent-browser&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Standalone&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;No&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Launches its own browser&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Playwright MCP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Default&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;No&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Creates an isolated browserContext&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;If you want your AI agent to use your existing logins&lt;/strong&gt; — to read your email, check your dashboards, manage your accounts — you need raw CDP or agent-browser connected via &lt;code&gt;--cdp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you want isolation&lt;/strong&gt; — for testing, scraping, or running untrusted automations — Playwright MCP or agent-browser standalone gives you that by default.&lt;/p&gt;

&lt;p&gt;Neither is "better." They're for different jobs. But most people don't know the difference exists.&lt;/p&gt;




&lt;h2&gt;
  
  
  How CDP Actually Works (The 2-Minute Version)
&lt;/h2&gt;

&lt;p&gt;Chrome's remote control has three layers. That's it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. List open tabs&lt;/strong&gt; (HTTP GET)&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:9222/json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Returns a JSON list of every tab with its title, URL, and WebSocket address.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Open a new tab&lt;/strong&gt; (HTTP PUT — changed in Chrome 145+)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; PUT &lt;span class="s2"&gt;"http://localhost:9222/json/new?https://example.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Send commands&lt;/strong&gt; (WebSocket)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Connect to a tab's WebSocket URL, then send:&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;method&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Page.navigate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;params&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;method&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Runtime.evaluate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;params&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;expression&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;document.title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;method&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Page.captureScreenshot&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the entire protocol. HTTP for tab management, WebSocket for commands. You can read, click, type, screenshot, and intercept network requests on any page.&lt;/p&gt;




&lt;h2&gt;
  
  
  Head-to-Head: Features Compared
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Playwright MCP (Microsoft)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; AI agents that need structured browser automation with safety guarantees.&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="c"&gt;# Add to Claude Code, Cursor, or any MCP-compatible AI tool&lt;/span&gt;
npx @playwright/mcp@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What it gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Snapshot mode&lt;/strong&gt; — Returns an accessibility tree. AI agents reference elements by &lt;code&gt;ref&lt;/code&gt; IDs instead of fragile CSS selectors&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Console + network&lt;/strong&gt; — Capture console logs and network requests&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Form filling&lt;/strong&gt; — Dedicated tools for clicks, fills, selects&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Screenshots&lt;/strong&gt; — PNG/JPEG with element-level targeting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session isolation&lt;/strong&gt; — Each connection gets its own clean context&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extension mode&lt;/strong&gt; — &lt;code&gt;--extension&lt;/code&gt; flag creates a bridge that CAN share sessions (shipped recently)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;29,000+ stars.&lt;/strong&gt; Isolated by default (intentional). Very active development.&lt;/p&gt;

&lt;h3&gt;
  
  
  agent-browser (Vercel Labs)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Fast, native CLI automation. AI agents that need speed and flexibility.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; agent-browser
agent-browser &lt;span class="nb"&gt;install&lt;/span&gt;  &lt;span class="c"&gt;# Downloads Chrome for Testing (first time)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What it gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;108+ commands&lt;/strong&gt; — &lt;code&gt;open&lt;/code&gt;, &lt;code&gt;click&lt;/code&gt;, &lt;code&gt;fill&lt;/code&gt;, &lt;code&gt;snapshot&lt;/code&gt;, &lt;code&gt;screenshot&lt;/code&gt;, &lt;code&gt;eval&lt;/code&gt;, &lt;code&gt;get text&lt;/code&gt;, &lt;code&gt;find role&lt;/code&gt;, &lt;code&gt;mouse&lt;/code&gt;, &lt;code&gt;network&lt;/code&gt;, &lt;code&gt;har&lt;/code&gt;, and more&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Annotated screenshots&lt;/strong&gt; — &lt;code&gt;--annotate&lt;/code&gt; labels interactive elements with numbered boxes for vision models&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Daemon architecture&lt;/strong&gt; — Browser persists between commands, chain with &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CDP connection&lt;/strong&gt; — &lt;code&gt;--cdp 9222&lt;/code&gt; connects to your real browser with all sessions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session persistence&lt;/strong&gt; — &lt;code&gt;--session-name myapp&lt;/code&gt; auto-saves and restores browser state&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-connect&lt;/strong&gt; — &lt;code&gt;--auto-connect&lt;/code&gt; finds your running Chrome automatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;iOS Simulator support&lt;/strong&gt; — Test on iPhone simulators via Appium&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HAR recording&lt;/strong&gt; — Capture full HTTP archive of all requests
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Chain commands — browser stays alive between them&lt;/span&gt;
agent-browser open example.com &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
agent-browser &lt;span class="nb"&gt;wait&lt;/span&gt; &lt;span class="nt"&gt;--load&lt;/span&gt; networkidle &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
agent-browser snapshot &lt;span class="nt"&gt;-i&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Built in Rust.&lt;/strong&gt; 78 releases in ~3 months. Created by Malte Ubl (Vercel CTO) and team. &lt;strong&gt;23,500+ stars.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Raw CDP (DIY)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Maximum control, authenticated workflows, custom integrations.&lt;/p&gt;

&lt;p&gt;No install needed — just talk to Chrome's HTTP/WebSocket API directly:&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="c"&gt;# Check what's running&lt;/span&gt;
curl http://localhost:9222/json/version

&lt;span class="c"&gt;# List your real open tabs&lt;/span&gt;
curl http://localhost:9222/json

&lt;span class="c"&gt;# Open a new tab (preserves all cookies and sessions)&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; PUT &lt;span class="s2"&gt;"http://localhost:9222/json/new?https://example.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;No framework. No dependencies.&lt;/strong&gt; Just HTTP requests and WebSocket messages. You can build a full browser automation tool in a few hundred lines of code. This is the lowest-level option — maximum power, maximum flexibility.&lt;/p&gt;




&lt;h2&gt;
  
  
  Using These Tools with AI Coding Agents
&lt;/h2&gt;

&lt;p&gt;These tools really shine when connected to AI coding agents like &lt;strong&gt;Claude Code&lt;/strong&gt;, &lt;strong&gt;Cursor&lt;/strong&gt;, &lt;strong&gt;OpenCode&lt;/strong&gt;, or &lt;strong&gt;Windsurf&lt;/strong&gt;. Here's how:&lt;/p&gt;

&lt;h3&gt;
  
  
  With Claude Code (MCP)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;.claude/mcp.json&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;"mcpServers"&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;"playwright"&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;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&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="s2"&gt;"@playwright/mcp@latest"&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="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;Now Claude Code can browse the web, fill forms, take screenshots, and read pages directly.&lt;/p&gt;

&lt;h3&gt;
  
  
  With agent-browser (CLI)
&lt;/h3&gt;

&lt;p&gt;Any AI agent that can run shell commands can use agent-browser:&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="c"&gt;# AI agent runs these commands to research a topic&lt;/span&gt;
agent-browser open &lt;span class="s2"&gt;"en.wikipedia.org/wiki/Chrome_DevTools"&lt;/span&gt;
agent-browser snapshot &lt;span class="nt"&gt;-i&lt;/span&gt;      &lt;span class="c"&gt;# Read the page content&lt;/span&gt;
agent-browser screenshot       &lt;span class="c"&gt;# See what it looks like&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  With agent-browser --cdp (Authenticated)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Connect to YOUR Chrome — AI reads pages as you&lt;/span&gt;
agent-browser &lt;span class="nt"&gt;--cdp&lt;/span&gt; 9222 snapshot &lt;span class="nt"&gt;-i&lt;/span&gt;
&lt;span class="c"&gt;# Now the AI can see your dashboards, email, authenticated content&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Connecting to Electron Apps
&lt;/h3&gt;

&lt;p&gt;Since Electron apps (VS Code, Slack, Discord) are built on Chromium, you can control them too:&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="c"&gt;# Launch any Electron app with CDP debugging&lt;/span&gt;
&lt;span class="s2"&gt;"/Applications/Visual Studio Code.app/Contents/MacOS/Electron"&lt;/span&gt; &lt;span class="nt"&gt;--remote-debugging-port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;9333

&lt;span class="c"&gt;# Connect agent-browser to VS Code&lt;/span&gt;
agent-browser &lt;span class="nt"&gt;--cdp&lt;/span&gt; 9333 snapshot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Landscape Is Moving Fast
&lt;/h2&gt;

&lt;p&gt;This isn't a niche topic anymore. The browser automation for AI agents space is exploding:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;browser-use&lt;/strong&gt; (78,000 stars) — Originally built on Playwright, switched to raw CDP in 2025 for speed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;chrome-devtools-mcp&lt;/strong&gt; (30,000+ stars) — Google's Chrome team released an official MCP server for CDP&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chrome 146&lt;/strong&gt; — Google is adding a native settings toggle for AI agent access via MCP, built right into Chrome&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The trend is clear: &lt;strong&gt;CDP is becoming the standard interface between AI agents and web browsers.&lt;/strong&gt; Google endorses it. Microsoft builds on it. Vercel bets on it.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to Use What
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Use Playwright MCP when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You need &lt;strong&gt;test isolation&lt;/strong&gt; — each run starts clean&lt;/li&gt;
&lt;li&gt;You're building &lt;strong&gt;automated testing pipelines&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;You want &lt;strong&gt;structured accessibility snapshots&lt;/strong&gt; for AI&lt;/li&gt;
&lt;li&gt;Security matters — you don't want the AI accessing your real sessions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use agent-browser when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You want &lt;strong&gt;speed&lt;/strong&gt; — native Rust, daemon architecture&lt;/li&gt;
&lt;li&gt;You need &lt;strong&gt;rich CLI commands&lt;/strong&gt; — 108+ built-in operations&lt;/li&gt;
&lt;li&gt;You want &lt;strong&gt;flexibility&lt;/strong&gt; — standalone OR connected to your browser&lt;/li&gt;
&lt;li&gt;You're working in &lt;strong&gt;cloud sandboxes&lt;/strong&gt; with parallel agent sessions&lt;/li&gt;
&lt;li&gt;You want the easiest path — just type commands and things happen&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use raw CDP when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You need &lt;strong&gt;authenticated sessions&lt;/strong&gt; — access your real logins&lt;/li&gt;
&lt;li&gt;You want &lt;strong&gt;zero dependencies&lt;/strong&gt; — just HTTP and WebSocket&lt;/li&gt;
&lt;li&gt;You're building &lt;strong&gt;custom integrations&lt;/strong&gt; specific to your workflow&lt;/li&gt;
&lt;li&gt;You need to &lt;strong&gt;intercept network requests&lt;/strong&gt; or capture auth tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Or combine them:
&lt;/h3&gt;

&lt;p&gt;The tools aren't mutually exclusive. You can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use &lt;strong&gt;raw CDP&lt;/strong&gt; or &lt;strong&gt;agent-browser --cdp&lt;/strong&gt; for authenticated workflows (read your email, manage dashboards)&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;agent-browser standalone&lt;/strong&gt; for fast scripted automation (fill forms, scrape data)&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;Playwright MCP&lt;/strong&gt; for isolated testing (run tests in clean contexts)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All talking to the same Chrome, via the same protocol.&lt;/p&gt;




&lt;h2&gt;
  
  
  Get Started Now
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Start Chrome with debugging enabled:&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="s2"&gt;"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"&lt;/span&gt; &lt;span class="nt"&gt;--remote-debugging-port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;9222
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Verify it works:&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:9222/json/version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; Pick your tool and try it:&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="c"&gt;# Option A: Playwright MCP (isolated, structured)&lt;/span&gt;
npx @playwright/mcp@latest &lt;span class="nt"&gt;--cdp-endpoint&lt;/span&gt; http://localhost:9222

&lt;span class="c"&gt;# Option B: agent-browser (fast, flexible)&lt;/span&gt;
npm i &lt;span class="nt"&gt;-g&lt;/span&gt; agent-browser
agent-browser open example.com &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; agent-browser snapshot &lt;span class="nt"&gt;-i&lt;/span&gt;

&lt;span class="c"&gt;# Option C: agent-browser connected to YOUR browser (authenticated)&lt;/span&gt;
agent-browser &lt;span class="nt"&gt;--cdp&lt;/span&gt; 9222 snapshot &lt;span class="nt"&gt;-i&lt;/span&gt;

&lt;span class="c"&gt;# Option D: Raw CDP (minimal, no dependencies)&lt;/span&gt;
curl http://localhost:9222/json  &lt;span class="c"&gt;# List your real tabs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You now have AI-ready browser control. Your logged-in sessions, your tabs, your data — all accessible to AI agents through the protocol that was hiding in plain sight.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Every Chrome browser ships with a remote control. Three major tools let AI agents use it. They all speak the same protocol, but they make fundamentally different choices about &lt;strong&gt;session isolation&lt;/strong&gt; — and that one choice determines whether your AI agent sees a login page or your actual dashboard.&lt;/p&gt;

&lt;p&gt;Now you know the difference. Build accordingly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Give Your AI Agent the Instructions
&lt;/h2&gt;

&lt;p&gt;Want your AI agent to already know how to use these tools? Each project publishes &lt;strong&gt;skills&lt;/strong&gt; — ready-made instruction files you can pass to Claude Code, Cursor, OpenCode, or any AI coding agent.&lt;/p&gt;

&lt;h3&gt;
  
  
  agent-browser skills
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add github:vercel-labs/agent-browser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/vercel-labs/agent-browser/tree/main/skills" rel="noopener noreferrer"&gt;5 skills available&lt;/a&gt;: general browser automation, QA/dogfood testing, &lt;strong&gt;Electron app control&lt;/strong&gt; (VS Code, Slack, Discord, Figma), Slack workspace automation, and Vercel Sandbox cloud sessions.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Electron skill&lt;/strong&gt; is especially powerful — it teaches your AI agent how to launch and control desktop apps like VS Code, Slack, or Figma through CDP. Your AI agent can read Slack messages, navigate VS Code, or interact with any Chromium-based desktop app.&lt;/p&gt;

&lt;h3&gt;
  
  
  chrome-devtools-mcp skills
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add github:anthropics/anthropic-cookbook chrome-devtools-mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/anthropics/anthropic-cookbook/tree/main/anthropic-skills/chrome-devtools-mcp/skills" rel="noopener noreferrer"&gt;5 skills available&lt;/a&gt;: core Chrome DevTools automation, CLI scripting, accessibility debugging, LCP performance optimization, and connection troubleshooting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Raw CDP skill
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add github:anthropics/anthropic-cookbook chrome-cdp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/anthropics/anthropic-cookbook/tree/main/anthropic-skills/chrome-cdp/skills" rel="noopener noreferrer"&gt;Skill available&lt;/a&gt;: Lightweight CDP CLI for live Chrome session control — connects to your real tabs with all cookies preserved. 13 commands, per-tab daemon architecture.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What are skills?&lt;/strong&gt; Skills are instruction files that teach AI agents how to use specific tools. Instead of explaining everything yourself, you install a skill and your agent instantly knows the commands, best practices, and common patterns. Think of them like a manual the AI reads before it starts working.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Find me at &lt;a href="https://timtech4u.dev" rel="noopener noreferrer"&gt;timtech4u.dev&lt;/a&gt; or &lt;a href="https://x.com/timtech4u" rel="noopener noreferrer"&gt;@timtech4u&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>chrome</category>
      <category>devtools</category>
    </item>
    <item>
      <title>Building AI Agents with Google ADK, FastAPI, and MCP</title>
      <dc:creator>Timothy Olaleke</dc:creator>
      <pubDate>Mon, 14 Apr 2025 23:58:36 +0000</pubDate>
      <link>https://dev.to/timtech4u/building-ai-agents-with-google-adk-fastapi-and-mcp-26h7</link>
      <guid>https://dev.to/timtech4u/building-ai-agents-with-google-adk-fastapi-and-mcp-26h7</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This guide will walk you through integrating Google's Agent Development Kit (ADK) with FastAPI and the Model Context Protocol (MCP). We'll start with basic ADK usage, then extend it to work with FastAPI, and finally create an MCP server from the same codebase.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What You'll Learn
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;How to build a basic ADK agent with tools&lt;/li&gt;
&lt;li&gt;How to serve your ADK agent using FastAPI&lt;/li&gt;
&lt;li&gt;How to expose your ADK tools through an MCP server&lt;/li&gt;
&lt;li&gt;Best practices for each integration pattern&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;You'll need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.10+ installed&lt;/li&gt;
&lt;li&gt;Access to Google's Gemini API (or another supported model)&lt;/li&gt;
&lt;li&gt;Basic knowledge of Python and APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Part 1: Getting Started with Google ADK
&lt;/h2&gt;

&lt;p&gt;The Google Agent Development Kit (ADK) is a framework for building AI agents with tool-using capabilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&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;google-adk python-dotenv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Creating a Basic Agent
&lt;/h3&gt;

&lt;p&gt;Let's create a simple agent with two tools: one to check the weather and another to get the current time in a city.&lt;/p&gt;

&lt;p&gt;First, create a directory structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;adk_agents/
  ├── multi_tool_agent/
  │   ├── __init__.py
  │   └── agent.py
  └── __init__.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Define your agent in &lt;code&gt;multi_tool_agent/agent.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;zoneinfo&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ZoneInfo&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.adk.agents&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_weather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Get the current weather in a city.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new york&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;report&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The weather in New York is sunny with 25°C.&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error_message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Weather for &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; unavailable.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_current_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Get the current time in a city.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;city_timezones&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new york&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;America/New_York&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;london&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Europe/London&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tokyo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Asia/Tokyo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;paris&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Europe/Paris&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;city_timezones&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;tz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ZoneInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city_timezones&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()])&lt;/span&gt;
            &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tz&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;report&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The current time in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; is &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%Y-%m-%d %H&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;S&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;Z&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;pass&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error_message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Time information for &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; unavailable.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Define the agent with the name "root_agent" (required by ADK)
&lt;/span&gt;&lt;span class="n"&gt;root_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weather_time_agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini-1.5-flash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Use your preferred Gemini model
&lt;/span&gt;    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Agent that provides weather and time information for cities.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;instruction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You help users with time and weather information for various cities.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;get_weather&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;get_current_time&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure to export the agent in &lt;code&gt;multi_tool_agent/__init__.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.agent&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;root_agent&lt;/span&gt;

&lt;span class="n"&gt;__all__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;root_agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Testing Your Agent
&lt;/h3&gt;

&lt;p&gt;Run the agent using the ADK CLI:&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;adk_agents
adk chat &lt;span class="nt"&gt;-a&lt;/span&gt; multi_tool_agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to test the GUI:&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;adk_agents
adk web &lt;span class="nt"&gt;-a&lt;/span&gt; multi_tool_agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Part 2: Integrating with FastAPI
&lt;/h2&gt;

&lt;p&gt;Now let's wrap our ADK agent in a FastAPI application, which makes it deployable as a web service.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&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;fastapi &lt;span class="s2"&gt;"uvicorn[standard]"&lt;/span&gt; sqlalchemy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Creating the FastAPI Wrapper
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;api.py&lt;/code&gt; in your root directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;uvicorn&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.adk.cli.fast_api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;get_fast_api_app&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&lt;/span&gt;

&lt;span class="c1"&gt;# Set up paths
&lt;/span&gt;&lt;span class="n"&gt;BASE_DIR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abspath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;AGENT_DIR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BASE_DIR&lt;/span&gt;  &lt;span class="c1"&gt;# Parent directory containing multi_tool_agent
&lt;/span&gt;
&lt;span class="c1"&gt;# Set up DB path for sessions
&lt;/span&gt;&lt;span class="n"&gt;SESSION_DB_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sqlite:///&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BASE_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sessions.db&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Create the FastAPI app using ADK's helper
&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_fast_api_app&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;agent_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;AGENT_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;session_db_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SESSION_DB_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;allow_origins&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;# In production, restrict this
&lt;/span&gt;    &lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Enable the ADK Web UI
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Add custom endpoints
&lt;/span&gt;&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/health&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;health_check&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;healthy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/agent-info&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;agent_info&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Provide agent information&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;multi_tool_agent&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;root_agent&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;root_agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;root_agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;root_agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tools&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;root_agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Starting FastAPI server...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;uvicorn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;9999&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="nb"&gt;reload&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Running the FastAPI Server
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python api.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Access the ADK Web UI at &lt;a href="http://localhost:9999/dev-ui" rel="noopener noreferrer"&gt;http://localhost:9999/dev-ui&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 3: Creating an MCP Server
&lt;/h2&gt;

&lt;p&gt;The Model Context Protocol (MCP) allows AI models to discover and use tools. Let's expose our tools via MCP.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&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;mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Adding MCP Server Support
&lt;/h3&gt;

&lt;p&gt;Add the following code to &lt;code&gt;api.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# MCP Server Implementation
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;types&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;mcp_types&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp.server.lowlevel&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Server&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp.server.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;InitializationOptions&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;mcp.server.stdio&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.adk.tools.function_tool&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FunctionTool&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.adk.tools.mcp_tool.conversion_utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;adk_to_mcp_tool_type&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_mcp_server&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Creates an MCP server exposing our agent&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s tools.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;multi_tool_agent.agent&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;get_weather&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;get_current_time&lt;/span&gt;

    &lt;span class="c1"&gt;# Wrap functions in FunctionTool objects
&lt;/span&gt;    &lt;span class="n"&gt;weather_tool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FunctionTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_weather&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;time_tool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FunctionTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_current_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Create MCP Server
&lt;/span&gt;    &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weather-time-mcp-server&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@app.list_tools&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;list_tools&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;mcp_types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;List available tools.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="c1"&gt;# Convert ADK tools to MCP format
&lt;/span&gt;        &lt;span class="n"&gt;mcp_tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nf"&gt;adk_to_mcp_tool_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weather_tool&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nf"&gt;adk_to_mcp_tool_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time_tool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;mcp_tools&lt;/span&gt;

    &lt;span class="nd"&gt;@app.call_tool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;mcp_types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TextContent&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Execute a tool call.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="c1"&gt;# Map tool names to functions
&lt;/span&gt;        &lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;weather_tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;weather_tool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;time_tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;time_tool&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# Execute the tool
&lt;/span&gt;                &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;run_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;tool_context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&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="n"&gt;mcp_types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TextContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&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="n"&gt;mcp_types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TextContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                    &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;
                &lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="k"&gt;else&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="n"&gt;mcp_types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TextContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Tool &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; not found&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="p"&gt;)]&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_mcp_server&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Run the MCP server over standard I/O.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_mcp_server&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stdio_server&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nf"&gt;as &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;write_stream&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;read_stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;write_stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;InitializationOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;server_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;server_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.1.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;capabilities&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_capabilities&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Add Command-Line Argument Support
&lt;/h3&gt;

&lt;p&gt;Update the main section to support both modes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Parse command line arguments
&lt;/span&gt;    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ADK Agent Server&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--mode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;web&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mcp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;web&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Run as web server or MCP server (default: web)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mcp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Run as MCP server
&lt;/span&gt;        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Starting MCP server mode...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;run_mcp_server&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Run as web server (default)
&lt;/span&gt;        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Starting Web server mode...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;uvicorn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
            &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
            &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;9999&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
            &lt;span class="nb"&gt;reload&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Testing the MCP Server
&lt;/h3&gt;

&lt;p&gt;Create a simple test file &lt;code&gt;test_mcp_server.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.adk.tools.mcp_tool&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MCPToolset&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp.client.stdio&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;StdioServerParameters&lt;/span&gt;

&lt;span class="n"&gt;API_SCRIPT_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abspath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_mcp_server&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Test the MCP server by checking tool availability.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="c1"&gt;# Connect to MCP server
&lt;/span&gt;    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exit_stack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;MCPToolset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;connection_params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;StdioServerParameters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;python&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;API_SCRIPT_PATH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--mode&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mcp&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# List available tools
&lt;/span&gt;        &lt;span class="n"&gt;tool_names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Found &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_names&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; tools: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_names&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Verify expected tools are available
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get_weather&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tool_names&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get_current_time&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tool_names&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SUCCESS: MCP server is working correctly!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WARNING: Not all expected tools were found!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;exit_stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;aclose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;test_mcp_server&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python test_mcp_server.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using Your MCP Server with Claude or other AI Assistants
&lt;/h2&gt;

&lt;p&gt;Once your MCP server is running, you can use it with any MCP-compatible AI assistant:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start your MCP server:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   python api.py &lt;span class="nt"&gt;--mode&lt;/span&gt; mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Configure your AI assistant (e.g., Claude) to use this MCP server. For Claude Desktop, you'd point it to your running MCP server.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The AI assistant will be able to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Discover the available tools (get_weather and get_current_time)&lt;/li&gt;
&lt;li&gt;Call these tools when appropriate during conversations&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Best Practices
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Environment Variables&lt;/strong&gt;: Store API keys and credentials in a &lt;code&gt;.env&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error Handling&lt;/strong&gt;: Add proper error handling in both tool implementations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logging&lt;/strong&gt;: Add comprehensive logging for debugging purposes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication&lt;/strong&gt;: Add authentication for production deployments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing&lt;/strong&gt;: Write unit tests for your tools and integration tests for your endpoints&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Common Issues and Solutions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Agent Not Found in FastAPI&lt;/strong&gt;: Make sure &lt;code&gt;AGENT_DIR&lt;/code&gt; is set to the parent directory containing your agent subdirectory&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP Tool Conversion Errors&lt;/strong&gt;: Ensure your tool functions have proper type hints&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Module Import Errors&lt;/strong&gt;: Check your &lt;code&gt;__init__.py&lt;/code&gt; files export the required objects&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;You now have a flexible codebase that can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run as a standalone ADK agent&lt;/li&gt;
&lt;li&gt;Serve as a FastAPI web application with a UI&lt;/li&gt;
&lt;li&gt;Function as an MCP server for AI assistants&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This approach gives you multiple deployment options from the same codebase, making your AI agent more versatile and accessible.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Journey to Serverless on Google Cloud Platform</title>
      <dc:creator>Timothy Olaleke</dc:creator>
      <pubDate>Mon, 09 Dec 2019 11:37:30 +0000</pubDate>
      <link>https://dev.to/timtech4u/journey-to-serverless-on-google-cloud-platform-4k7n</link>
      <guid>https://dev.to/timtech4u/journey-to-serverless-on-google-cloud-platform-4k7n</guid>
      <description>&lt;p&gt;Google Cloud’s serverless platform lets you write code your way without worrying about the underlying infrastructure. Deploy functions or apps as source code or as containers. Build full-stack serverless applications with Google Cloud’s storage, databases, machine learning, and more while using your favorite language, runtimes, frameworks, and libraries.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Eoa0Av-e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1569458451294/UO5DJ2QE5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Eoa0Av-e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1569458451294/UO5DJ2QE5.png" alt="gcp-serverless-options.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Serverless doesn’t mean there are no servers, instead, it takes the workload of managing a server which removes the needs for handling the configuring, provisioning, load balancing, sharding, scaling, and infrastructure management, so you can focus on building great applications.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--blC-DnrF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1569457562961/spgVmjhvO.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--blC-DnrF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1569457562961/spgVmjhvO.png" alt="serverless-intro.png"&gt;&lt;/a&gt;&lt;br&gt;
Serverless computing (also known as &lt;strong&gt;No-Ops&lt;/strong&gt;) distributes majorly across operational and developer experiences, thus ensuring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; There are no servers to manage or provision&lt;/li&gt;
&lt;li&gt; Bills are optimized to what you use&lt;/li&gt;
&lt;li&gt; Focus on Code&lt;/li&gt;
&lt;li&gt; Scale-up fast&lt;/li&gt;
&lt;li&gt; Scale down &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--niVxr5P1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1569457975511/UBdj7Ep-7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--niVxr5P1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1569457975511/UBdj7Ep-7.png" alt="serverless-more-than-faas.png"&gt;&lt;/a&gt;&lt;br&gt;
Function as a Service (FaaS) and serverless are often used interchangeably, however serverless has more to offer than FaaS.&lt;br&gt;
FaaS platforms take a function from developers, build it into an app, and deploy it in the cloud. *Serverless is much more than just FaaS. *&lt;/p&gt;

&lt;h1&gt;
  
  
  GCP Serverless Compute Options
&lt;/h1&gt;

&lt;p&gt;Google Cloud provides serverless compute options that can fit into your kind of application. These options span across &lt;strong&gt;&lt;em&gt;Event-based applications, HTTP applications and also Containerized applications&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cloud Functions
&lt;/h2&gt;

&lt;p&gt;Google Cloud Functions is a lightweight compute solution for developers to create single-purpose, stand-alone functions that respond to cloud events without the need to manage a server or runtime environment.&lt;br&gt;
Common Cloud Functions use cases could be Data processing, Webhooks, Lightweight APIs, Mobile backend, IoT&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--L9BQ3sPJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1569458612124/9xgprMBri.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--L9BQ3sPJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1569458612124/9xgprMBri.png" alt="cf-triggers.png"&gt;&lt;/a&gt;&lt;br&gt;
Cloud Functions allows you to trigger your code from Google Cloud Platform, Firebase, and Google Assistant, or call it directly from any web, mobile, or backend application via HTTP. &lt;/p&gt;

&lt;p&gt;Cloud Functions supports code written in JavaScript (Node.js), Python, and Go. There are no new languages, tools, or frameworks to learn. All you need to do is bring code — including native libraries you bring to the platform. &lt;/p&gt;

&lt;p&gt;Cloud Functions provides a connective layer of logic that lets you write code to connect and extend cloud services. Listen and respond to a file upload to Cloud Storage, a log change, or an incoming message on a Cloud Pub/Sub topic.&lt;/p&gt;

&lt;p&gt;Learn more about  &lt;a href="https://cloud.google.com/functions"&gt;Cloud Functions.&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  App Engine
&lt;/h2&gt;

&lt;p&gt;App Engine is a fully managed, serverless platform for developing and hosting web applications at scale. You can choose from several popular languages, libraries, and frameworks to develop your apps, then let App Engine take care of provisioning servers and scaling your app instances based on demand. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--t4Y9s6mW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1569458637995/Blk3a4HU6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--t4Y9s6mW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1569458637995/Blk3a4HU6.png" alt="runtimes-cf-gae.png"&gt;&lt;/a&gt;&lt;br&gt;
Quickly build and deploy applications using many of the popular languages like Java, PHP, Node.js, Python, C#, .Net, Ruby and Go or bring your own language runtimes and frameworks if you choose.&lt;/p&gt;

&lt;p&gt;Application Versioning in App Engine allows you easily host different versions of your app, easily create development, test, staging, and production environments.&lt;/p&gt;

&lt;p&gt;Traffic Splitting in App Engine enables incoming requests to different app versions, A/B test and do incremental feature rollouts.&lt;/p&gt;

&lt;p&gt;App Engine also provides security to your applications by defining access rules with App Engine firewall and leverage managed SSL/TLS certificates by default on your custom domain at no additional cost.&lt;/p&gt;

&lt;p&gt;Learn more about &lt;a href="https://cloud.google.com/appengine/"&gt;App Engine&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Cloud Run is a managed compute platform that automatically scales your stateless containers. Cloud Run is serverless: it abstracts away all infrastructure management, so you can focus on what matters most — building great applications. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gUTMFn5v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1569459607685/Fj7Bie5QZ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gUTMFn5v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1569459607685/Fj7Bie5QZ.png" alt="cr-knative.png"&gt;&lt;/a&gt;&lt;br&gt;
Cloud Run is built on the Knative open-source project, enabling the portability of your workloads across platforms.&lt;br&gt;
Knative offers features like scale-to-zero, autoscaling, in-cluster builds, and eventing framework for cloud-native applications on Kubernetes. &lt;/p&gt;

&lt;p&gt;Cloud Run (fully managed) allows you to deploy stateless containers without having to worry about the underlying infrastructure. Your workloads are automatically scaled up or down to zero depending on the traffic to your app. You only pay when your app is running, billed to the nearest 100th millisecond.&lt;/p&gt;

&lt;p&gt;Cloud Run for Anthos (on-premises) abstracts away complex Kubernetes concepts, allowing developers to easily leverage the benefits of Kubernetes and serverless together. It provides access to custom machine types, additional networking support, and Cloud Accelerators. &lt;/p&gt;

&lt;p&gt;Cloud Run automatically scales up or down from zero to N depending on traffic. It also allows for custom domain mappings and provides SSL for free.&lt;/p&gt;

&lt;p&gt;Learn more about &lt;a href="https://cloud.google.com/run"&gt;Cloud Run&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  DevOps and Monitoring with serverless
&lt;/h1&gt;

&lt;p&gt;Google Cloud integrate other products into deploying a serverless application and also in monitoring logs.&lt;/p&gt;

&lt;p&gt;Deployment to serverless environments such as Cloud Function and App Engine uses &lt;a href="https://cloud.google.com/cloud-build/"&gt;Cloud Build&lt;/a&gt; in the background and monitoring logs for serverless applications also leverages on &lt;a href="https://cloud.google.com/stackdriver/"&gt;Stackdriver&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vJ-awWWe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1569461168731/Edw0hk_Vt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vJ-awWWe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1569461168731/Edw0hk_Vt.png" alt="DevOps with Serverless.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cloud Build is a service that lets you easily create continuous integration and delivery (CI/CD) pipelines for your serverless applications.&lt;/p&gt;

&lt;p&gt;Stackdriver is a monitoring service that aggregates metrics, logs, and events for your serverless applications running on Google Cloud or on-premises.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--erjvf694--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1569461148473/bP5tMqjHW.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--erjvf694--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1569461148473/bP5tMqjHW.png" alt="summary.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Useful Links
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt; &lt;a href="https://cloud.google.com/serverless-options/"&gt;Google Cloud - Choosing a Serverless Option&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt; &lt;a href="https://cloud.google.com/functions/docs/functions-framework"&gt;Cloud Function - Functions Framework&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt; &lt;a href="https://cloud.google.com/appengine/docs/the-appengine-environments"&gt;App Engine -  Choosing an App Engine environment&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt; &lt;a href="https://github.com/ahmetb/cloud-run-faq"&gt;Ahmetb - Cloud Run FAQ&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt; &lt;a href="https://firebase.google.com/docs/hosting/serverless-overview"&gt;Serve dynamic content and host microservices using Firebase Hosting&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>serverless</category>
      <category>appengine</category>
      <category>cloudrun</category>
      <category>cloudfunction</category>
    </item>
  </channel>
</rss>
