Skip to content

feat(mcp): revalidate cached local-path indexes on query#78

Merged
amondnet merged 5 commits into
mainfrom
amondnet/mcp-revalidate
Jul 11, 2026
Merged

feat(mcp): revalidate cached local-path indexes on query#78
amondnet merged 5 commits into
mainfrom
amondnet/mcp-revalidate

Conversation

@amondnet

@amondnet amondnet commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Closes #74. Ports upstream semble#211 (41c36f7).

Problem

The MCP session cache (IndexCache) served a local path's index for the lifetime of the server without ever rechecking it. If the repo's files changed mid-session, search / find_related returned stale results.

Change

  • index.rs — extract source_fingerprint(source, content) (the same content-hash oracle load_or_build_index already used to validate the on-disk cache) as a pub helper. load_or_build_index now calls it too — no behavior change, just deduplication.
  • mcp.rs — each IndexCache entry now carries the fingerprint captured at build time plus a revalidate_after cooldown (build_duration × MIN_REVALIDATE_FACTOR, factor = 3, mirroring upstream's _MIN_REVALIDATE_FACTOR). On get, once the cooldown has elapsed, the live fingerprint is recomputed; a mismatch evicts the entry so it rebuilds. Git URLs report None and are never revalidated. The cooldown keeps a slow-to-build repo from being re-walked on every query.
  • The LoadOrBuild seam gains a fingerprint() method.

Why the fingerprint (not upstream's mtime+file-set)

Upstream get_validated_cache re-stats every file against a stored write-time and compares file sets. csp already uses a content hash of the source tree as its cache-validity oracle (stronger and simpler), so revalidation reuses that same signal rather than introducing an mtime path.

Verification

  • cargo fmt --all ✅ · cargo clippy --all-targets --all-features -- -D warnings ✅ (clean)
  • cargo test --workspace ✅ — 259 lib + 20 CLI tests pass, incl. new:
    • cache_revalidates_stale_local_path — within-cooldown reuse despite drift, then stale rebuild past the cooldown, then fresh-match reuse
    • cache_git_url_not_revalidated — git URLs served from cache even when the (local-only) fingerprint changes
  • CLI smoke: csp search still returns results after the load_or_build_index refactor.

Notes

  • The cache runs synchronously under the server's tokio::sync::Mutex; the fingerprint recompute is blocking like the existing build path already is (offloading to a worker is a pre-existing, separate concern noted in CLAUDE.md).

Summary by cubic

Revalidates cached local-path MCP indexes on query using a content-hash fingerprint with a build-time-scaled cooldown (min 2s), so results refresh after file changes without re-walking on every query. Git URLs are never revalidated; the cooldown resets after a successful check, and cache-hit lookup is streamlined.

  • New Features

    • IndexCache stores a build-time fingerprint and revalidate_after; past the cooldown, a changed live fingerprint evicts and rebuilds; a match resets the cooldown.
    • Cooldown = last build duration × MIN_REVALIDATE_FACTOR (3), with a 2s minimum to avoid near-every-query checks on fast builds.
    • LoadOrBuild adds fingerprint(); git URLs return None and are not revalidated.
  • Refactors

    • Extracted source_fingerprint(source, content) and reused it in load_or_build_index.
    • Streamlined cache-hit path in IndexCache::get to reduce map operations.

Written for commit 904f046. Summary will update on new commits.

Port semble#211: an MCP session-cache entry for a local path is now
rechecked against its live source fingerprint on each `get`, so an entry
is rebuilt once its files change instead of serving stale results for the
lifetime of the server. Git URLs (URL+ref keyed) are never revalidated.

- index.rs: extract `source_fingerprint(source, content)` (the same
  content-hash oracle load_or_build_index already used) as a pub helper;
  load_or_build_index now calls it too (no behavior change).
- mcp.rs: IndexCache entries carry the build-time fingerprint plus a
  `revalidate_after` cooldown (build duration x MIN_REVALIDATE_FACTOR=3),
  so a slow-to-build repo isn't re-walked on every query. On get, past
  the cooldown, a changed fingerprint evicts the entry -> rebuild. The
  LoadOrBuild seam gains a `fingerprint` method (None for git URLs).
- Tests: cache_revalidates_stale_local_path (cooldown skip + stale
  rebuild + fresh-match reuse) and cache_git_url_not_revalidated.

Refs #74
@codacy-production

codacy-production Bot commented Jul 11, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 9 complexity · 0 duplication

Metric Results
Complexity 9
Duplication 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request implements a cache revalidation mechanism for local source trees using content-hash fingerprints and a build-time-scaled cooldown period to prevent frequent disk I/O. The review feedback correctly identifies a performance issue: when a revalidation check succeeds (the fingerprint matches), the cooldown timer is not reset, causing subsequent queries to repeatedly perform fingerprint checks. The reviewer suggests adding a revalidate_cooldown field to CacheEntry and updating the revalidate_after timestamp upon successful revalidation.

Comment thread crates/csp/src/mcp.rs
Comment thread crates/csp/src/mcp.rs Outdated

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 2 files

Architecture diagram
sequenceDiagram
    participant Client as MCP Client
    participant Server as MCP Server
    participant Cache as IndexCache
    participant Seam as LoadOrBuild (DiskLoadOrBuild)
    participant FP as source_fingerprint()
    participant FS as File System

    Client->>Server: search/find_related(source, query)
    Server->>Cache: get(source, git_ref)
    Cache->>Cache: computeKey(source, git_ref)
    Cache->>Cache: look up entry in index map

    alt entry exists AND local path (fingerprint is Some) AND cooldown elapsed
        Cache->>Seam: fingerprint(source, content)
        Seam->>FP: source_fingerprint(source, content)
        FP->>FS: collect_source_files() + compute_content_hash()
        FS-->>FP: hash value
        FP-->>Seam: Some(hash)
        Seam-->>Cache: fingerprint value
        Cache->>Cache: compare with stored fingerprint
        alt mismatch → stale
            Cache->>Cache: shift_remove entry (evict)
        else match
            Note over Cache: entry stays valid
        end
    else
        Note over Cache: skip revalidation (cooldown active or git URL)
    end

    Cache->>Cache: shift_remove key (if not already evicted)
    alt entry still present
        Cache->>Cache: re-insert for LRU touch
        Cache-->>Server: Arc<CspIndex>
    else entry evicted or missing
        Note over Cache: LRU eviction if at capacity
        Cache->>Seam: load_or_build(source, content, git_ref)
        Seam->>Seam: build index (walk filesystem / clone repo)
        Seam-->>Cache: CspIndex
        Note over Cache: record build_duration = now - start
        Cache->>Seam: fingerprint(source, content) again
        Seam-->>Cache: live fingerprint (None for git)
        Cache->>Cache: create CacheEntry(fingerprint, revalidate_after = now + build_duration × 3)
        Cache->>Cache: insert into cache
        Cache-->>Server: Arc<CspIndex>
    end

    Server-->>Client: search results
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread crates/csp/src/mcp.rs Outdated
@codecov

codecov Bot commented Jul 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.71795% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
crates/csp/src/indexing/cache_orchestrator.rs 90.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@greptile-apps

greptile-apps Bot commented Jul 11, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds content-hash-based revalidation to the MCP session index cache (IndexCache), so stale local-path indexes are detected and rebuilt after file changes. A build-time-scaled cooldown (build_duration × 3, floor 2 s) prevents re-walking the source tree on every query; the cooldown resets after each successful fingerprint match. Git URLs are never revalidated.

  • cache_orchestrator.rs: Extracts the existing source-tree hash logic into a public source_fingerprint helper; load_or_build_index delegates to it (no behavior change).
  • mcp.rs: CacheEntry gains fingerprint, revalidate_after, and revalidate_cooldown fields; IndexCache::get checks the live fingerprint once the cooldown expires, evicting and rebuilding on mismatch or advancing the cooldown on a match.
  • Two new unit tests cover the stale-rebuild path and the git-URL bypass.

Confidence Score: 5/5

Safe to merge; the revalidation logic is well-structured and the previous cooldown-reset gap was addressed.

The core revalidation logic is correct: stale entries are evicted, fresh entries have their cooldown advanced, git URLs are never revalidated, and the LRU invariant is preserved. The only noteworthy issue is a redundant filesystem walk after each rebuild, which is bounded by MIN_REVALIDATE_COOLDOWN and does not affect correctness.

No files require special attention; mcp.rs carries the most logic but its test coverage is thorough.

Important Files Changed

Filename Overview
crates/csp/src/mcp.rs Adds CacheEntry struct with fingerprint + cooldown fields; IndexCache::get now revalidates local-path entries past the cooldown. The cooldown is restored after a successful match. Tests cover stale-path rebuild and git-URL bypass.
crates/csp/src/indexing/cache_orchestrator.rs Extracts source_fingerprint as a public helper; load_or_build_index now delegates to it — pure refactor, no behavior change.
crates/csp/src/indexing/index.rs Re-exports source_fingerprint alongside existing public symbols — trivial one-liner addition.

Reviews (3): Last reviewed commit: "perf(mcp): enforce minimum revalidation ..." | Re-trigger Greptile

Comment thread crates/csp/src/mcp.rs Outdated
@codspeed-hq

codspeed-hq Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 5 untouched benchmarks


Comparing amondnet/mcp-revalidate (904f046) with main (1d2ea9e)

Open in CodSpeed

@amondnet amondnet self-assigned this Jul 11, 2026
@amondnet

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a mechanism to revalidate cached local source indexes against their live content-hash fingerprints in IndexCache::get after a build-time-scaled cooldown period, preventing stale cache hits when local files change. Git URLs are excluded from this revalidation. The review feedback suggests optimizing IndexCache::get to reduce redundant map lookups and duplicate logic when retrieving, validating, and updating cache entries.

Comment thread crates/csp/src/mcp.rs Outdated
@amondnet

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a local source tree fingerprinting and revalidation mechanism for the index cache to detect staleness on query, using a build-time-scaled cooldown to prevent frequent directory re-walking. Git URLs are excluded from this revalidation. The feedback highlights two performance improvements: establishing a minimum cooldown threshold to prevent excessive revalidation when builds are extremely fast, and eliminating redundant fingerprint calculations that occur immediately after a cache miss or rebuild.

Comment thread crates/csp/src/mcp.rs Outdated
Comment thread crates/csp/src/mcp.rs
@amondnet

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist 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.

Code Review

본 풀 리퀘스트는 로컬 소스 트리의 변경 사항을 감지하여 캐시된 인덱스를 재검증하는 source_fingerprint 기능 및 쿨다운 메커니즘을 도입합니다. 리뷰에서는 디스크 캐시 히트 시 build_elapsed가 매우 짧아져 쿨다운이 최소치(2초)로 고정됨에 따라 대규모 저장소에서 빈번한 디렉토리 순회 및 해싱 병목이 발생할 수 있는 문제와, load_or_build 호출 직후 fingerprint를 중복 계산하는 성능 비효율성을 지적하고 개선 방향을 제시했습니다.

Comment thread crates/csp/src/mcp.rs
Comment thread crates/csp/src/mcp.rs
@sonarqubecloud

Copy link
Copy Markdown

@amondnet amondnet merged commit 74911c9 into main Jul 11, 2026
14 checks passed
@amondnet amondnet deleted the amondnet/mcp-revalidate branch July 11, 2026 12:46
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.

parity(#211): revalidate cached MCP indexes on query (evict stale local-path caches)

1 participant