Evidence Trail

Gemini CLI Makes MCP List-Changed Notifications Resilient

March 11, 2026 / Daily Edition / 3 source signals.

repo google-gemini/gemini-cli main
3 source signals 1 repo 352fb0c
> 352fb0c / March 11, 2026 / Daily Edition
Read Story Open Edition

Reporter Notes

Notes — 2026-03-11

Insight

Gemini CLI hardens MCP list-changed handling by registering notification listeners even when servers fail to advertise listChanged, then coalesces bursts of refreshes and retries tool discovery once to dodge race conditions where servers notify before tool lists are ready.

Evidence

  • PR summary (TDD + robustness + retries): https://github.com/google-gemini/gemini-cli/pull/21050
  • Commit: 352fb0c97680b11578f88277f9e0265aa9a2191a
  • mcp-client.ts registers handlers even if listChanged capability is false:
  • Logs “did not declare 'listChanged' capability. Listening anyway for robustness...” and sets notification handlers for tools/resources/prompts.
  • refreshTools() coalesces bursts (pendingToolRefresh) and retries discovery once after 500ms when tool names match.

Snippets


// packages/core/src/tools/mcp-client.ts
if (capabilities.tools.listChanged) {
  debugLogger.log(`Server '${this.serverName}' supports tool updates. Listening for changes...`);
} else {
  debugLogger.log(
    `Server '${this.serverName}' has tools but did not declare 'listChanged' capability. Listening anyway for robustness...`,
  );
}
this.client.setNotificationHandler(
  ToolListChangedNotificationSchema,
  async () => {
    debugLogger.log(`🔔 Received tool update notification from '${this.serverName}'`);
    await this.refreshTools();
  },
);

// packages/core/src/tools/mcp-client.ts
if (this.isRefreshingTools) {
  debugLogger.log(
    `Tool refresh for '${this.serverName}' is already in progress. Pending update.`,
  );
  this.pendingToolRefresh = true;
  return;
}
...
if (toolNamesMatch && !this.pendingToolRefresh) {
  debugLogger.log(
    `No tool changes detected for '${this.serverName}'. Retrying once in 500ms...`,
  );
  const retryDelay = 500;
  await new Promise((resolve) => setTimeout(resolve, retryDelay));
  newTools = await this.discoverTools(this.cliConfig, {
    signal: abortController.signal,
  });
}

Commands / Outputs

  • gsio search q "list_changed notifications" -p google-gemini/gemini-cli --output summary --top-k 5
  • gh pr view 21050 -R google-gemini/gemini-cli --json title,url,author,mergedAt,body
  • git show 352fb0c97680b11578f88277f9e0265aa9a2191a --stat

Sources — 2026-03-11