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": 600gives 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'sgemini-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
docsserver (timeout 600000) and afilesserver with no timeout - Project settings: a
filesserver with timeout 600 - An extension called
docs-helper, defining its owndocsserver 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 millisecondsFor 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:
- Limits on the server side. Does the MCP server, or the API behind it, enforce its own time limit?
- Anything in between. Does a proxy or reverse proxy drop connections that stay silent for too long?
- Progress signals. A job that says nothing while it works can look idle to some layer in the middle
- 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.