Gemini Sidekick — a Gemini MCP connector for Claude
A remote MCP connector that gives Claude access to yourGoogle Gemini models, deployed as a single stateless Cloudflare Worker on the free tier.It works everywhere your Claude account goes: claude.ai web, the mobile app, Claude Desktop,and every Claude Code project.
The rule this whole thing is built around:Gemini is a gap-filler and a verifier, never an author. Claude writes every answer itself —its own reasoning, creativity, brainstorming, and judgment, from start to finish. Thisconnector exists only to (a) do things Claude cannot do at all (generate and edit images,reach exotic models) and (b) make Claude's own answers better through independentverification and contrasting perspectives. Never use it to outsource Claude's thinking,writing, ideation, or analysis. Critique-then-refine, not merge-two-drafts.
This rule is sent to Claude on every connection, and it's also in the two paste-blocks below(claude-profile-instructions.md andclaude-code-config.md). Keep it in all three.
What it gives Claude
| Tool | What it's for |
|---|---|
list_gemini_models |
Live model discovery + recommended defaults. Nothing is hardcoded; the live list is the source of truth, so new models on your key appear automatically. |
generate_image |
Generate (Imagen-class quality, or a fast/cheap draft) and iteratively edit — feed a result's URL back in to refine "add a red bandana / warmer light / bigger logo" indefinitely, branching whenever you want. |
gemini_audit |
Independent, structured cross-model audit of important output Claude produced (issues with severity + location + suggested fix + a confidence) so Claude can surgically fix only what's wrong. |
ask_gemini |
A genuine second opinion to contrast with Claude's own answer. Multi-turn. |
gemini_disagree |
Asks a fast and a strong model the same thing and surfaces only where they diverge — divergence is the signal. |
gemini_digest |
Offloads a very large input (big PDF, whole codebase, long transcript, YouTube URL) to Gemini's huge context window and returns a compact structured summary. |
gemini_grounded |
Gemini with Google Search grounding as a second, independent search engine to cross-check facts. |
gemini_raw |
Escape hatch to any model/method on your key (music, robotics, video, TTS, embeddings, future models), including polling long-running video jobs to completion. |
Every generated/edited image is hosted at a stable, unguessable URL and returned both as amarkdown image and as a plain clickable link — because many clients (claude.ai web/mobile)won't render an inline image from a connector.
Deploy
Prerequisites: a Cloudflare account (free) and aGoogle AI Studio API key with billing enabled. The onlyvalue you generate yourself is one random string — your connector secret:
openssl rand -hex 24
Forking this for your own use? Your
src/code needs no changes. The one fork-specificvalue is the KV namespace id: a KV namespace belongs to a single Cloudflare account, so theid committed here (the original author's) won't work in yours. Set yours — no CLI required:
- Fork the repo on GitHub.
- Cloudflare dashboard → Storage & Databases → KV → Create a namespace (any name) → copyits Namespace ID.
- In your fork on GitHub, open
wrangler.jsonc, replace theidon theMEDIAbinding withthat value, and commit.(Original author: already set — skip this.)
Option A — Connect the repo to Cloudflare (recommended; no local tooling)
After this one-time setup, every push to main auto-builds and deploys (edit → update.sh → live).
Create the application. Dashboard → Workers & Pages → Create application →Import a repository → GitHub → install/authorize the Cloudflare GitHub app → pick your repo.
On the "Set up your application" screen (open Advanced settings to see everything):
- Build command:
npm install - Deploy command:
npx wrangler deploy - Non-production branch deploy command:
npx wrangler versions upload(the default — leave it) - Path:
/· API token: leave the auto-created one - Variable name / Variable value → leave BLANK. ⚠️ This box adds build variables; secretsentered here never reach the running worker. The real secrets go in step 3.
- Click Deploy and let the first build finish.
- Build command:
Add the two secrets to the WORKER (runtime — not the build). Open the worker →Settings → Variables and secrets → + Add, each as type Secret (encrypted):
GEMINI_API_KEY— your AI Studio keyCONNECTOR_SECRET— the random string from above (the secret in your URL)
Save. (
DAILY_CALL_CAPandIMAGE_TTL_SECONDSare already listed as plaintext — they come fromwrangler.jsonc.) Until the secrets are set, the URL returns a clean "Server not configured."Get your connector URL. On the worker's page the address is shown at the top and behind theVisit button:
https://gemini-mcp.<your-subdomain>.workers.devgemini-mcpis the workernamefromwrangler.jsonc;<your-subdomain>is your account'sworkers.dev subdomain (the same across all your workers). That bare URL should say "Gemini Sidekickconnector is running." Your connector URL is it plus/<CONNECTOR_SECRET>/mcp:https://gemini-mcp.<your-subdomain>.workers.dev/<CONNECTOR_SECRET>/mcp
Verify with npm run smoke (see "Verify it works"), then add it inclaude.ai. Push future changes — which auto-deploy — with:
bash scripts/update.sh "what you changed" # macOS / Linux / Git Bash
scripts\update.bat "what you changed" # Windows
Option B — Deploy from your machine with the CLI (alternative)
Needs Node 18+.
npm install
npx wrangler login
# Forkers: create your KV namespace, then put the printed id in wrangler.jsonc (kv_namespaces[0].id),
# replacing the committed value. Say NO if wrangler offers to "add it on your behalf".
npx wrangler kv namespace create MEDIA
npx wrangler deploy # creates the worker and prints its URL
npx wrangler secret put GEMINI_API_KEY # your AI Studio key
npx wrangler secret put CONNECTOR_SECRET # the random string from above
Connect it to claude.ai (web + mobile + Desktop share this)
Settings → Connectors → Add custom connector → paste the full connector URL above →save. Because connectors live on your account, it's immediately available in the web app, themobile app, and Claude Desktop. Then paste claude-profile-instructions.mdinto Settings → Profile → "Instructions for Claude."
Custom connectors require a Claude plan that supports them (Pro/Max/Team/Enterprise).
Connect it to Claude Code (every project, automatically)
claude mcp add --scope user --transport http gemini \
"https://gemini-mcp.<your-subdomain>.workers.dev/<CONNECTOR_SECRET>/mcp"
--scope user makes it available in every project. Then add the philosophy + usage blockfrom claude-code-config.md to your global ~/.claude/CLAUDE.md.
Verify it works (after deploy)
Automated smoke test — speaks MCP straight to your deployed Worker and exercises each toolagainst real Gemini, with no Claude in the loop (so it's deterministic and scriptable). It makesa few cents of real calls.
GEMINI_MCP_URL="https://gemini-mcp.<your-subdomain>.workers.dev/<CONNECTOR_SECRET>/mcp" npm run smoke
npm run smoke -- --cheap— protocol + model list + one flash call only (near-free).npm run smoke -- --no-image— skip the (priciest) image generate/edit calls.npm run smoke -- --full— also rungemini_disagree(3 calls) andgemini_digest.
Exit code is 0 only if every step passed. The secret stays in the env var — it's never writtento the repo.
Then a 2-minute manual check in claude.ai web or mobile — the one thing the script can'tverify is client-side rendering. Ask it to "generate an image of a fox, then make it wear a hat,"and confirm you get a working clickable link at each step (the inline image won't render there —that's expected, and exactly why the link matters).
Troubleshooting
- Deploys fine, but every call errors or returns "Server not configured": your secrets are setas Build variables, not runtime. Move
GEMINI_API_KEYandCONNECTOR_SECRETto the worker →Settings → Variables and secrets (encrypted), then redeploy. - First build fails on the KV namespace: the
idinwrangler.jsoncisn't in your account.Create your own withnpx wrangler kv namespace create MEDIAand replace it. - "MEDIA assigned to multiple KV Namespace bindings": there are two
MEDIAentries — wranglerappended one when you accepted its "add it on your behalf" prompt. Keep a single binding. - 404 on the connector URL: the secret in the path doesn't match
CONNECTOR_SECRET. The bareworker URL (no path) should say "Gemini Sidekick connector is running."
Configuration (optional)
Set in wrangler.jsonc under vars, then redeploy:
DAILY_CALL_CAP— a circuit breaker."0"(default) disables it;"200"refusesbillable Gemini calls after 200 in a UTC day. Approximate (counted in KV), meant to stop arunaway loop, not to do accounting. Listing models and polling operations don't count.IMAGE_TTL_SECONDS— how long hosted images live in KV (default2592000= 30 days).Images must outlive an editing session so you can keep refining a result across turns.
Free-tier KV allows ~1,000 writes/day. Each generated/edited image is one write (and,if
DAILY_CALL_CAPis on, each billable call is one more). That's plenty for personal use,but it's the limit you'd hit first if something loops.
Billing safety — read this
Billing is on, so a runaway loop costs money, not just quota. The DAILY_CALL_CAP above isa guard rail. Your real safety net is a budget alert: in theGoogle Cloud Console → Billing → Budgets &alerts, create a budget on the project behind your AI Studio key with email alerts at, say,50% / 90% / 100%. Do this — the connector secret lives in the URL, so if it ever leaks, abudget alert is what tells you.
Security notes
- Auth is an unguessable secret as the first URL path segment (claude.ai's connector UIcan't send static auth headers, so this is what actually works). It's compared inconstant time, and the Gemini key is stored as a Cloudflare secret, never in the URL.
- Image links are decoupled from the connector secret. Images are served from
/img/<id>where<id>is its own 144-bit unguessable token — so sharing an image linknever leaks your connector secret. - The image route can't become an XSS vector. Only safe raster types (PNG/JPEG/WebP/GIF)are served inline; anything else (e.g. SVG) is forced to download, under a strict
Content-Security-PolicyandX-Content-Type-Options: nosniff. - Model names and API methods are validated against allow-lists before they're ever placed inan API URL path (injection protection).
- Stored image bytes are copied into an exact-length buffer, so no pooled/shared memory canleak into a stored image or a subsequent edit.
Publishing this repo (it's public-safe)
Nothing secret is committed, so this repo is safe to make public:
GEMINI_API_KEYandCONNECTOR_SECRETare never in the repo. They live as encryptedCloudflare Worker secrets;.dev.vars(local only) is gitignored.- The KV namespace id in
wrangler.jsoncis not a secret — it's an account-scoped resourcehandle that does nothing without your Cloudflare credentials.
If someone forks this, they create their own KV namespace, replace that one id, and set their owntwo secrets — see Deploy. Nothing tied to you exposes anything sensitive.
Local development & tests
cp .dev.vars.example .dev.vars # fill in a fake key + a test secret
npm run dev # wrangler dev (local workerd + local KV)
npm test # unit + integration tests (transport, security, defaults)
npm run typecheck # tsc --noEmit over src/
npm run build # dry-run bundle, no deploy
Should there be a text-to-speech tool? (the open question)
Decision: no dedicated TTS tool — it's covered by the gemini_raw escape hatch, whose audiooutput is auto-hosted at a link. Reasoning:
- TTS is a real capability gap (Claude can't synthesize speech), so it belongs somewhere.But unlike image editing, it has no iterative loop and no model-shape juggling that abespoke tool would simplify — it's a single
generateContentcall with aspeechConfig,whichgemini_rawalready expresses directly. - Audio can't render inline in Claude's clients anyway, so it needs the same "host it andreturn a link" treatment images get.
gemini_rawalready does that automatically for anyinline media in a response, so TTS audio comes back as a clickable URL with zero extrasurface. - You removed TTS once already, which signals it's low-frequency for you. A leaner tool listalso serves the "proactive but never naggy" goal: fewer tools means Claude routes to theright one more reliably.
- If you find yourself reaching for it constantly, promoting it to a first-class tool later isa small change (it would mostly be the hosting wiring, which already exists).
So it's available today via gemini_raw (model + generateContent + a speechConfig body),just not as its own tool.