Connect Your App to Gemini Spark with MCP — ContentBuffer guide

Connect Your App to Gemini Spark with MCP

K
Kodetra Technologies··9 min read Intermediate

Summary

Build a standard MCP server in Python that plugs into Gemini Spark and Claude Desktop.

Why every service suddenly needs an MCP server

On May 19, 2026 at Google I/O, Google shipped Gemini Spark: a personal AI agent that lives on a dedicated cloud VM and keeps working 24/7 even when your phone is asleep. It drafts email, books tables, and runs recurring workflows on your behalf. The detail that matters for builders is buried in the keynote: Spark talks to the outside world through the Model Context Protocol (MCP). The launch partners (Canva, OpenTable, Instacart) plugged in as MCP servers, and Adobe, Spotify, and dozens more are arriving over the summer.

That reframes a question every product team is now asking. If an always-on agent is going to act for your users, can it reach your service? If the answer is no, you are invisible to the fastest-growing surface in consumer AI. The good news: the same MCP server that makes you reachable from Spark also works in Claude Desktop, ChatGPT's connector layer, and any other MCP client. You build once.

This guide walks through building a real MCP server in Python from an empty folder, testing it with the official Inspector, and wiring it into both Claude Desktop and Gemini Spark. The code below is runnable and was executed against the live MCP Python SDK before publishing, including a real network call to the US National Weather Service.

What MCP actually is (in 60 seconds)

MCP is an open JSON-RPC protocol that standardizes how an LLM application discovers and calls external capabilities. A client (Spark, Claude Desktop) connects to a server (your code) and the server advertises three kinds of things:

  • Tools — functions the model can call, like book_table(date, party_size). This is what you will use most.
  • Resources — read-only data the client can load into context, addressed by a URI like config://units.
  • Prompts — reusable prompt templates the user can invoke by name.

The protocol detail you never have to hand-write is the JSON schema for each tool. The SDK generates it from your Python type hints and docstring, and that schema is exactly what the agent reads to decide when and how to call you. Good type hints and a clear docstring are not cosmetic; they are the contract the model reasons over.

Prerequisites

  • Python 3.10 or newer (the SDK uses modern typing features).
  • Comfort with async/await is helpful but not required for the server code itself.
  • For the Gemini Spark step: a Google AI Ultra account with Spark access (rolling out to US testers now at $249.99/mo). The Claude Desktop step is free and is the fastest way to verify your server.
  • Roughly 20 minutes.

Step 1 — Install the SDK and scaffold a project

Create a clean folder and install the MCP SDK with its CLI extras. The CLI ships the mcp command, which includes the local Inspector you will use to test.

mkdir spark-mcp && cd spark-mcp
python3 -m venv .venv
source .venv/bin/activate
pip install "mcp[cli]" httpx

Two packages: mcp[cli] is the protocol SDK plus the Inspector; httpx is just for the example tool, which calls a real weather API. Swap it for whatever your service already uses.

Step 2 — Write your first tool

Create server.py. FastMCP is the high-level server class. Decorate a plain function with @mcp.tool() and it becomes a callable agent tool. Notice there is no manual schema anywhere: the signature state: str and the docstring are what the agent sees.

import sys
import httpx
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("weather-tools")

@mcp.tool()
def get_alerts(state: str) -> str:
    """Get active weather alerts for a US state.

    Args:
        state: Two-letter US state code, e.g. "CA" or "NY".
    """
    url = f"https://api.weather.gov/alerts/active?area={state.upper()}"
    headers = {"User-Agent": "spark-mcp-demo/1.0",
               "Accept": "application/geo+json"}
    try:
        r = httpx.get(url, headers=headers, timeout=15.0)
        r.raise_for_status()
        features = r.json().get("features", [])
    except Exception as e:
        return f"Could not fetch alerts: {e}"

    if not features:
        return f"No active weather alerts for {state.upper()}."

    lines = []
    for f in features[:5]:
        p = f["properties"]
        lines.append(f"{p.get('event')}: {p.get('headline')}")
    return "\n".join(lines)

if __name__ == "__main__":
    print("starting weather-tools MCP server", file=sys.stderr)
    mcp.run()

Three things make this agent-ready. The docstring's first line tells the model when to use the tool. The Args: block documents each parameter the agent must fill. And the function returns a plain string, which the SDK wraps into a protocol response automatically. Always return something on the error path too; a tool that raises gives the agent nothing to recover from, while a returned error string lets it retry or apologize.

Step 3 — Add a resource

Resources are read-only context the client can pull in. Add a units resource so the agent knows your service speaks Fahrenheit and mph without guessing:

@mcp.resource("config://units")
def units() -> str:
    """Default measurement units for this service."""
    return "temperature=fahrenheit; wind=mph"

The string in @mcp.resource(...) is the URI the client uses to address it. Use a scheme that signals intent (config://, db://, file://). Resources are for data the model reads; tools are for actions the model takes. Keep that line clean and agents behave far more predictably.

Step 4 — Run and test locally with the Inspector

Before touching any agent, prove the server works in isolation. The SDK ships a browser-based Inspector:

mcp dev server.py

This launches your server and opens the Inspector (typically at http://127.0.0.1:6274). Click Connect, open the Tools tab, select get_alerts, enter a state code like KS, and run it. You should see the live result come back. When I ran exactly this server against the real API, the call returned:

TOOLS: ['get_alerts']
RESOURCES: ['config://units']
get_alerts("KS") -> Special Weather Statement: Special Weather Statement
  issued May 26 at 2:36PM CDT by NWS Dodge City KS

If the tool shows up and returns data here, the hard part is done. Every MCP client after this is just a config entry pointing at the same file.

Step 5 — Connect it to Claude Desktop

Claude Desktop is the quickest real-client check because it is free. Open its config file (Settings > Developer > Edit Config, or edit it directly) and register your server under mcpServers:

{
  "mcpServers": {
    "weather-tools": {
      "command": "/absolute/path/to/spark-mcp/.venv/bin/python",
      "args": ["/absolute/path/to/spark-mcp/server.py"]
    }
  }
}

Use absolute paths to both the venv Python and the script; the client launches the process from an unknown working directory. Restart Claude Desktop, look for the tools icon, then ask: "Are there any active weather alerts in Kansas?" Claude will call get_alerts and answer from the live result.

Step 6 — Connect it to Gemini Spark

Spark consumes MCP servers as connectors. For local stdio servers during development you point Spark at the same command and args you used for Claude Desktop, through the Spark connector settings. For production, Spark expects a reachable server over the streamable HTTP transport rather than stdio, since Spark itself runs in Google's cloud and cannot spawn a process on your laptop. Switch transports with one argument:

if __name__ == "__main__":
    # Local dev / Claude Desktop:
    # mcp.run()

    # Production for cloud agents like Gemini Spark:
    mcp.run(transport="streamable-http")

With streamable HTTP the server listens on a port (default http://127.0.0.1:8000/mcp) that you deploy behind HTTPS and register as a Spark connector URL. The tool definitions, schemas, and behavior are identical to what you already tested. Google has been explicit that a standard MCP server should work unchanged across Claude Desktop, Spark, and other clients; if yours only works in one, it is doing something non-standard.


A worked example: making a booking service agent-ready

The weather tool is read-only. The interesting case for Spark is an action the agent takes on the user's behalf. Here is a trimmed booking tool with the patterns that matter in production: typed enums via Literal, input validation that returns (not raises), and a confirmation string the agent can read back to the user.

from typing import Literal
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("bookings")

@mcp.tool()
def book_table(
    restaurant_id: str,
    date: str,
    time: str,
    party_size: int,
    seating: Literal["indoor", "outdoor", "any"] = "any",
) -> str:
    """Reserve a table at a restaurant.

    Args:
        restaurant_id: Internal restaurant identifier.
        date: ISO date, e.g. "2026-05-30".
        time: 24h time, e.g. "19:30".
        party_size: Number of guests (1-12).
        seating: Seating preference.
    """
    if not (1 <= party_size <= 12):
        return "party_size must be between 1 and 12."
    # ... call your real booking backend here ...
    confirmation = "BK-48217"
    return (f"Booked {party_size} at {restaurant_id} on {date} {time} "
            f"({seating}). Confirmation {confirmation}.")

Why Literal matters: the SDK turns it into an enum in the tool schema, so the agent can only pass indoor, outdoor, or any. You constrain the model at the protocol level instead of validating bad free-text after the fact. When a user tells Spark "book dinner for four outside on Saturday," the agent fills these fields, calls the tool, and reads back Confirmation BK-48217. That confirmation string is the whole point: give agents something concrete to relay.

Common pitfalls and gotchas

  • Printing to stdout breaks stdio servers. The stdio transport is stdout; a stray print("debug") corrupts the JSON-RPC stream and the client silently disconnects. Always log to stderr: print(msg, file=sys.stderr) or use the logging module, which writes to stderr by default.
  • Blocking calls stall the event loop. A synchronous httpx.get is fine for a demo, but in a busy server use async def tools with httpx.AsyncClient so one slow API call does not freeze every other request.
  • Vague docstrings produce a tool the agent never calls. The model picks tools purely from the name, the first docstring line, and the parameter docs. "Does stuff with data" gets ignored; "Get active weather alerts for a US state" gets called correctly.
  • Relative paths fail in client config. Clients launch your server from an unpredictable directory. Use absolute paths to both the interpreter and the script, and absolute paths for any files your tools read.
  • Wrong transport for the client. stdio is for local clients that spawn a process (Claude Desktop). Cloud agents like Spark cannot spawn a local process, so production needs streamable-http behind HTTPS.
  • No auth on a write tool. A tool that books or charges must verify identity. Do not ship book_table or send_payment over an unauthenticated public endpoint; gate it with OAuth or a per-user token that your backend checks.
  • Returning huge blobs. Tool results land in the model's context window. Summarize or paginate; a 50KB JSON dump wastes tokens and degrades the agent's reasoning.

Quick reference

TaskCode / command
Create servermcp = FastMCP("name")
Define a tool@mcp.tool() above a typed function
Define a resource@mcp.resource("scheme://path")
Run locally (Claude Desktop)mcp.run() # stdio
Run for cloud agents (Spark)mcp.run(transport="streamable-http")
Test interactivelymcp dev server.py -> Inspector
Log safelyprint(msg, file=sys.stderr)
Constrain an argumentparam: Literal["a", "b"]

Next steps

  • Replace the demo tool with one real action from your product and test it in Claude Desktop today.
  • Switch to streamable-http, deploy behind HTTPS, and add OAuth before exposing any write tool.
  • Add per-tool input validation that returns friendly error strings so agents recover gracefully.
  • Read the MCP spec's authorization section before registering a connector that touches user data.
  • Register the deployed server as a Gemini Spark connector and confirm the same tools appear unchanged.

MCP is becoming the USB-C port of the agent era: one server, every client. Spark just made that port consumer-scale. The teams that expose a clean MCP surface now are the ones agents will actually use.

Comments

Subscribe to join the conversation...

Be the first to comment