◉GEMINI LABJP
●ARGON — gemini-4-argon is generally available as of 10/13, aimed at complex coding, agents, math and science●DEEP RES — deep-research-pro-preview-12-2025 shuts down 10/23, 5 days left. Move to deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 is GA (10/06). gemini-3.1-flash-image is deprecated with no shutdown date yet●MCP — One write-up cut an 85,000-token tool result to 310 tokens by wrapping it in an ADK FunctionTool●FLASH — gemini-3.7-flash and 3.5-flash now route automatically to newer Flash models (10/08)●NEW — Stop Re-Reading Every Crash Report: Gre…●ARGON — gemini-4-argon is generally available as of 10/13, aimed at complex coding, agents, math and science●DEEP RES — deep-research-pro-preview-12-2025 shuts down 10/23, 5 days left. Move to deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 is GA (10/06). gemini-3.1-flash-image is deprecated with no shutdown date yet●MCP — One write-up cut an 85,000-token tool result to 310 tokens by wrapping it in an ADK FunctionTool●FLASH — gemini-3.7-flash and 3.5-flash now route automatically to newer Flash models (10/08)●NEW — Stop Re-Reading Every Crash Report: Gre…
Articles/Dev Tools
⟐ Dev Tools/2026-10-19Intermediate

I Set the MCP Timeout to 10 Minutes and It Still Cuts Off at 1 — Tracing Which Layer Decides the Value

You raised an MCP server's timeout in Gemini CLI and it still ends early. A small script lists the value from every config layer, catching unit slips and extension overrides, plus the order I check things in.

Gemini CLI10MCP7timeout6settings.jsontroubleshooting90Python52

I was rereading reports of an MCP call that gets cut off at one minute even though timeout was raised to ten, and I opened my own settings file. My hand stopped. There were more places to put timeout than I had remembered.

The setting changed, yet nothing seemed to change. My first instinct is always to blame the CLI. But starting from that suspicion usually costs me an hour. What I should check first is simpler: the value is not decided in one place.

Similar symptoms ("it ends sooner than the configured time") have been reported in the gemini-cli issue tracker. The cause will differ from machine to machine, so I won't pretend to name one culprit here. What I can offer is a way to rule things out in order, and a script to help.

What unit is timeout in, and where can it live?

An MCP server entry takes a timeout key, measured in milliseconds. If you leave it out, the default of 600,000 (ten minutes) applies.

Two things trip people up:

  • The unit is milliseconds. Writing "timeout": 600 gives you 0.6 seconds, and meaning ten minutes while typing 600 is an easy slip
  • There are several places to define it: user settings at ~/.gemini/settings.json, project settings at .gemini/settings.json, and an extension's gemini-extension.json

When the same server name appears in more than one place, which value wins depends on layer precedence. That precedence can change between versions, so instead of memorising it I put every layer's value side by side and read them.

A small script that lines up every layer

This script reads user settings, project settings and installed extensions, then lists timeout per server name. It does not declare a winner; it only reports disagreements and suspected unit mistakes.

#!/usr/bin/env python3
"""List where each MCP server's timeout is defined."""
import json
import sys
from pathlib import Path
 
DEFAULT_MS = 600_000  # documented default when nothing is set
 
 
def humanize(ms):
    if ms < 1000:
        return f"{ms} ms"
    if ms < 60_000:
        return f"{ms / 1000:g} s"
    return f"{ms / 60_000:g} min"
 
 
def layers(home: Path, project: Path):
    yield "user", home / ".gemini" / "settings.json"
    yield "project", project / ".gemini" / "settings.json"
    ext_dir = home / ".gemini" / "extensions"
    if ext_dir.is_dir():
        for p in sorted(ext_dir.glob("*/gemini-extension.json")):
            yield f"extension:{p.parent.name}", p
 
 
def collect(home: Path, project: Path):
    found = {}  # server name -> [(layer, file, timeout or None)]
    for label, path in layers(home, project):
        if not path.is_file():
            continue
        try:
            data = json.loads(path.read_text(encoding="utf-8"))
        except json.JSONDecodeError as e:
            print(f"! {path}: invalid JSON ({e})", file=sys.stderr)
            continue
        for name, conf in (data.get("mcpServers") or {}).items():
            found.setdefault(name, []).append((label, path, conf.get("timeout")))
    return found
 
 
def report(found):
    suspicious = 0
    for name, rows in sorted(found.items()):
        print(f"[{name}]")
        for label, path, t in rows:
            shown = "unset (default)" if t is None else f"{t} -> {humanize(t)}"
            flag = ""
            if isinstance(t, (int, float)) and t < 1000:
                flag = "  <- written in seconds? the unit is milliseconds"
                suspicious += 1
            print(f"  {label:<24} {shown}{flag}")
        values = {t for _, _, t in rows if t is not None}
        if len(values) > 1:
            print(f"  * {len(values)} different values defined. Check precedence")
            suspicious += 1
        if not values:
            print(f"  (not set in any layer -> default {humanize(DEFAULT_MS)})")
    return suspicious
 
 
if __name__ == "__main__":
    home = Path(sys.argv[1]) if len(sys.argv) > 1 else Path.home()
    project = Path(sys.argv[2]) if len(sys.argv) > 2 else Path.cwd()
    sys.exit(1 if report(collect(home, project)) else 0)

Why not pick a winner? Precedence rules move with releases, and baking them into a script means old knowledge quietly produces a wrong answer. I would rather print the facts and leave the judgement to a person.

I ran it against deliberately conflicting settings

To check the mechanics I built sample settings by hand. They are fixtures for testing, not my real configuration.

  • User settings: a docs server (timeout 600000) and a files server with no timeout
  • Project settings: a files server with timeout 600
  • An extension called docs-helper, defining its own docs server with timeout 60000

The output:

[docs]
  user                     600000 -> 10 min
  extension:docs-helper    60000 -> 1 min
  * 2 different values defined. Check precedence
[files]
  user                     unset (default)
  project                  600 -> 600 ms  <- written in seconds? the unit is milliseconds

For docs, ten minutes and one minute sit side by side. If the extension's value were the one in effect, the symptom would look a lot like a cut-off at exactly one minute. For files, writing 600 produced a 0.6-second timeout. Both are visible at a glance, before anyone needs to suspect the CLI.

The fixture only shows that the script can catch a disagreement. Whether a one-minute cut on your machine comes from an extension is something only a run on your machine can tell you.

One detail worth saying out loud: the exit code is non-zero whenever the script finds anything suspicious, so you can drop it into a pre-commit hook for a shared project and let it complain before a teammate ever hits the cut-off.

If the listing finds nothing

Then the cause sits outside the settings files. I check in this order:

  1. Limits on the server side. Does the MCP server, or the API behind it, enforce its own time limit?
  2. Anything in between. Does a proxy or reverse proxy drop connections that stay silent for too long?
  3. Progress signals. A job that says nothing while it works can look idle to some layer in the middle
  4. The CLI version. Check release notes and issues for a known bug

For step 3, splitting the work into small chunks that report progress is the dependable fix. A design that doesn't stay silent for long survives more layers than a bigger timeout does.

The line I draw

One habit came out of this: after changing a setting, read back the value that is actually in effect. Not the value I wrote, but the one in use. I now treat that as part of the same task.

Next time you touch a timeout, run find_mcp_timeouts.py once before you edit anything. For the setup itself, see Gemini CLI with MCP Servers; for trimming tools once they pile up, The Day I Unplugged an MCP Server for the First Time; and for checking known bugs first, Stop Re-Reading Every Crash Report.

Share

Thank You for Reading

Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

⟐ Dev Tools2026-09-21
The Day I Unplugged an MCP Server for the First Time — How I Decide Which Tools Stay On
Keep adding MCP servers and quality degrades well before you hit the API ceiling of 512 function declarations. Here is how I use includeTools and excludeTools in Gemini CLI to decide which tools stay enabled, and how I put the rest away.
⟐ Dev Tools2026-10-17
Hung Jobs Fail Quietly — Wrapping a Scheduled Gemini Call With a Timeout, an Empty-Output Check, and a Lock
An outer shell script that protects a cron-driven `gemini -p` call from three quiet failures: hanging on a response, exiting 0 with empty output, and overlapping with the previous run. Includes exit codes and timings measured with stand-in commands, and how far `timeout` reaches into child processes.
⟐ Dev Tools2026-09-29
Gemini CLI Kept Saying 'API key not valid' After I Gave It a New Key — It Was Reading an Old One From a Third Place
I switched Gemini CLI from Google login to a paid API key, and the 400 'API key not valid' error would not go away. The key can come from four places — the shell, a project .env, a home .env, and settings.json — and a short script that lights them up in order narrows the cause to one.