Skip to content

fix(mcp): preserve cache invalidation during refresh - #4807

Open
fengting124 wants to merge 2 commits into
openai:mainfrom
fengting124:fix/mcp-cache-invalidation-race
Open

fix(mcp): preserve cache invalidation during refresh#4807
fengting124 wants to merge 2 commits into
openai:mainfrom
fengting124:fix/mcp-cache-invalidation-race

Conversation

@fengting124

@fengting124 fengting124 commented Sep 1, 2026

Copy link
Copy Markdown

This pull request fixes an MCP tools-cache race where an in-flight
list_tools() refresh could clear a newer invalidation or leave a retained
stale schema authoritative for call_tool() validation.

Each refresh captures the current cache generation and only publishes its
result when that generation remains current. While the cache is dirty, local
required-parameter validation is skipped until a later refresh establishes a
clean authoritative snapshot.

A deterministic concurrency regression covers the stale refresh result,
retained cache snapshot, dirty-cache call-through, subsequent refresh, and
restored clean-cache validation.

Validation:

  • targeted concurrency and validation regression
  • full tests/mcp/test_caching.py
  • related filtering, pagination, retry, and validation tests
  • make format
  • make lint
  • make typecheck
  • make tests
  • git diff --check

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 36e85b1dff

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/agents/mcp/server.py
Comment on lines +1501 to +1503
if refresh_generation == self._tools_cache_generation:
self._tools_list = tools
self._cache_dirty = False

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep tool-call validation aligned with refresh results

When invalidate_tools_cache() is called while a refresh is in flight and the server changed the required parameters of an existing tool, this branch leaves the old _tools_list in place while list_tools() returns the newly fetched schema. A subsequent call_tool() validates against that old list in _validate_required_parameters() and can reject a valid invocation for missing a parameter that the returned schema no longer requires, before the request reaches the server. Avoid validating against the dirty prior cache while preserving the pending invalidation.

AGENTS.md reference: AGENTS.md:L149-L149

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in cbbcd49b. Dirty tool caches are no longer used for local required-parameter validation, so a stale retained schema cannot reject calls while a newer invalidation is pending. The regression now covers the stale refresh result, dirty-cache call-through, the subsequent clean refresh, and restored validation from the new authoritative cache.

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep them coming!

Reviewed commit: cbbcd49b53

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@sylvesterkaczmarek sylvesterkaczmarek left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The generation check handles invalidation during a refresh, but it does not prevent an older concurrent refresh from overwriting a newer one when both started at the same generation.

For Stdio/SSE servers _serialize_session_requests is false, so two callers can enter list_tools() while the cache is dirty. Both capture the same _tools_cache_generation. If refresh A starts first, refresh B starts second and returns a newer schema first, B publishes it and clears _cache_dirty; when the slower A later completes, its generation still matches and it publishes its older result over B.

This leaves the cache authoritative but stale, including the local required-parameter validation this PR is trying to protect.

Could refresh publication also carry a monotonically increasing refresh sequence (publish only the latest-started refresh), or coalesce/serialize cache refreshes? A deterministic test can start A and B after one invalidation, let B return the new schema first, then release A with the old schema and assert the cache remains on B's result.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants