fix(mcp): rebuild the prompt prefix when the connector set changes

The `## MCP servers` table lives inside the frozen system prefix, which
PrefixCache holds for twenty idle minutes. Refreshing a user's global-access
snapshot fixed what `mcp.tools()` offers but left the table describing the
world before the change, so an admin could enable a connector, ask for it in an
open conversation, and be told in good faith that it does not exist — with the
tools sitting right there. Same gap on a reinstall, whose new
llm_short_description reached the runtime and not the prompt.

Both refreshes now call `invalidate_prefixes()` on the live contexts they were
already iterating, the seam the skill tools use. Order matters and runs against
the intuition: `render_mcp_list` renders the runtime's in-RAM state, not the
DB, so the invalidation goes last — after the snapshot refresh and after the
servers restart. Rebuild earlier and the prefix is repopulated from the very
descriptions being replaced, with nothing left to invalidate it a second time.
In the reinstall that means waiting out a dependency install; those users were
already reading a stale table, and an early rebuild would only freeze the stale
one in place.

Also warn when a feed's connector.json and index disagree on the integer
version. The manifest silently wins, so if the index is the lower of the two
the strict `feed > installed` comparison is false forever: the connector never
offers an Update and nothing anywhere says why.
This commit is contained in:
Daniele
2026-08-10 13:20:26 +01:00
parent 59549d2b3b
commit 2dad4824c9
3 changed files with 48 additions and 1 deletions
+13
View File
@@ -393,6 +393,19 @@ fn card_of(h: &Hydrated, installed: bool, installed_version: Option<i64>) -> Mar
let source = norm_source(&h.entry, &h.manifest);
let doc = h.manifest.docs.first().cloned().unwrap_or_default();
// Prefer the manifest's version trio, falling back to the index entry's.
// A disagreement between the two is a malformed feed and is otherwise completely
// invisible: the manifest silently wins, `installed_version` keeps whatever the
// install snapshotted, and if the feed's index is the lower of the two the strict
// `feed > have` below is false forever — the connector never offers an Update and
// nothing anywhere says why. Say it once, in the log.
if let (Some(m), Some(e)) = (h.manifest.version, h.entry.version) {
if m != e {
tracing::warn!(
connector = %h.entry.id, manifest_version = m, index_version = e,
"marketplace feed version desync: connector.json and the index disagree; the manifest wins",
);
}
}
let version = h.manifest.version.or(h.entry.version);
let version_string = h.manifest.version_string.clone().or_else(|| h.entry.version_string.clone());
let version_release_date = h.manifest.version_release_date.clone().or_else(|| h.entry.version_release_date.clone());