Ever wanted Gemini Spark to talk to something Google doesn’t own?
I use Claude at work every day, and for good reason. It’s great at what it does, and Anthropic’s ecosystem makes wiring up third-party tools as simple as clicking a button. But for my personal setup, I’m a complete Google fanboy. I use Gemini Spark constantly because I love that it can run unattended in the background and has deep, native ties into the rest of my Google life (Gmail, Calendar, Drive, and Docs).
The trouble starts the moment you step outside that walled garden. I spent a Saturday trying to point Spark at my own MCP server and got “Account linking required” about two hundred times in a row. No useful error, no logs worth reading, just a login loop that would not close. I came pretty close to handing a middleman $20 a month to make it somebody else’s problem.
I didn’t, and it turns out the whole thing runs for free. Here’s what it took.
What is Model Context Protocol?
If you haven’t run into it yet, the Model Context Protocol (MCP) does for LLMs roughly what Services did for the Mac. Instead of every app building a bespoke integration with every other app, there’s one agreed-on way to expose a capability and one agreed-on way to call it. Write the tool once, and any model that speaks MCP can discover it, pull context from it, and act on it.
Why won’t Spark just connect to an existing MCP server?
Claude lets you click a pre-built connector or point at a local command, and it just works. Spark is a different animal:
- Almost no built-in connectors: Spark has practically zero out-of-the-box integrations for third-party tools compared to Claude’s catalog.
- Strict HTTPS and OAuth 2.0 requirements: Gemini Spark only connects to remote MCP servers over public HTTPS endpoints protected by OAuth 2.0.
That second requirement is the real roadblock. It immediately rules out standard local stdio MCP servers (the kind that run on your laptop for desktop apps). Worse, it also breaks a lot of hosted community MCP servers that “ought to” work out of the box, because they don’t implement the exact OAuth flow Spark expects.
How do you connect your own tools without paying for a connector service?
If you search around for ways to solve this, the internet will happily point you toward connector clearinghouses like Composio. Those services work fine if you’re an enterprise with a company card, but for personal use, paying a $20-$50/month middleman tax just to let an AI model check a repository or run a script on a Saturday afternoon makes zero sense.
I wanted something lightweight, fully under my own control, and free (or at most a few pennies a month).
Almost none of this is my work. FastMCP is the Python framework doing the actual heavy lifting: it handles the protocol, the tool registration, and the OAuth scaffolding, and you can stand up a working MCP server with it in about a dozen lines. Everything I added is glue, plus workarounds for Spark’s particular quirks.
The rest of the recipe is Google Cloud Run to host it and Google Cloud Firestore to hang onto tokens. Cloud Run gives you an instant HTTPS endpoint with automatic SSL and scales all the way down to zero instances when idle. The always-free tier covers 2 million requests a month, and it stays pretty cheap as you ramp up past that. A personal MCP server that only runs when you ask it a question sits comfortably inside the free tier. When you aren’t chatting with Spark, you pay literally $0.
What does it take to make Cloud Run and FastMCP actually work?
Deploying a FastMCP container to Cloud Run sounds simple on paper, and it is right up until Spark tries to log in. Four gotchas, all of which cost me an embarrassing amount of a Saturday:
- The PKCE vs. Basic Auth loop: FastMCP advertises
client_secret_basicsupport in its metadata by default. When Spark sees this, it tries to use HTTP Basic Auth instead of PKCE (none), which is the infinite “Account linking required” loop from the top of this post. The fix is monkeypatching FastMCP’s metadata generator at startup to strip the basic auth methods. It’s a few lines. Finding them took most of the afternoon. - Dynamic Client ID loss on scale-to-zero: Gemini Spark registers dynamic client IDs during setup. But when Cloud Run scales down to zero, that in-memory registration disappears with the container. The next cold boot rejects Spark with “Client ID not found,” which is a fun one to debug because it only happens after you walk away for twenty minutes. A custom
ResilientGoogleProvidersynthesizes valid client records on the fly whenever a cold container starts. Related and nastier: if your OAuth consent screen is set to External and left in Testing status, Google expires your refresh tokens after seven days. Everything works beautifully on day one and then dies the following week withinvalid_grant, looking for all the world like a storage bug. Publish the app (no verification review needed for basic scopes) or use Internal if you’re on Workspace. - Lost tokens on ephemeral disks: FastMCP writes OAuth refresh tokens to local disk by default. On Cloud Run, container filesystems are ephemeral and vanish on restarts and new deploys, so every deploy logs you out. Backing FastMCP with an encrypted Firestore collection keeps tokens alive across container lifecycles without paying for a persistent virtual disk.
- Acting as the user, not the service account: By default, Cloud Run executes API calls using its default machine service account, which has access to precisely none of your personal data. Instead, the server pulls the caller’s OAuth bearer token out of the incoming request, so tools run with your real permissions.
The whole boilerplate, Dockerfile, and deployment scripts are in the spark-cloud-run-mcp repo on GitHub, with step-by-step setup instructions and the code behind all four fixes above.
Is it secure?
Reasonably, in that it runs on your own OAuth permissions rather than some third party’s. But it was all vibe-coded on a weekend, so be careful before you point an MCP server at anything that touches your wallet. Start with things where the worst case is embarrassing rather than expensive.
What can you do with it?
Once the bridge is in place, you can hook Spark into pretty much anything without ongoing overhead. A few ways I’m using it today:
- Analytics and data diagnostics: Custom tools that pull reporting and run checks for some personal projects.
- A real GitHub integration: Gemini is inexplicably missing a built-in GitHub integration (and hopefully one they’ll fix, since it’s seamless in with Antigravity), so I wired up my own MCP server to search repos, pull commits, inspect PRs, and review diffs directly from chat.
- Connecting something new: These days I can usually find an existing MCP server for whatever I want to talk to, run the
cloud-run-spark-mcpskill that’s included in the repo, and walk through a bit of configuration. It’s pretty dialed in and repeatable at this point.
It’s not as slick as clicking a connector button in Claude, and I wouldn’t hand it to someone who’d rather not think about OAuth at all. But it costs nothing, it runs on my own permissions instead of a vendor’s, and connecting something new is mostly a matter of pointing the skill at it. For a weekend project that mostly stays out of my way, that’s a fine trade.
This blog’s been empty for years because my blogging has mostly shown up at McGaw.io instead.
