skills: cache orchestrator resources per thread (#28336)

## Why

Hosted orchestrator skills are read through the remote MCP resource
server. Within one thread, the same catalog or skill resource can be
requested multiple times by prompt injection and the `skills.list` /
`skills.read` tools. Re-fetching adds latency and can make those
surfaces observe different remote contents during the same thread.

This is a follow-up to #28333: orchestrator skills remain limited to
threads without a local executor, and those threads now get a stable
per-thread view of the remote skill data they use.

## What changed

- Reuse the existing per-thread orchestrator catalog snapshot for
`skills.list` and `skills.read` availability checks.
- Cache successful orchestrator resource reads by authority, package,
and resource so prompt injection and tool calls share the same contents.
- Keep the cache memory-only and bounded to 100 resources and 8 MiB per
thread.
- Leave host and executor skill reads unchanged, and do not cache failed
remote reads.

## Verification

- Extended the app-server MCP resource integration test to read the same
hosted skill resource twice and verify that the remote server receives
one read.
- The same test verifies that catalog discovery and the selected skill's
main prompt are each fetched only once per thread.
This commit is contained in:
jif
2026-06-15 20:20:19 +02:00
committed by GitHub
parent ee40dddbf6
commit 0afe559318
7 changed files with 340 additions and 55 deletions
+1
View File
@@ -4,6 +4,7 @@ pub use elicitation::ElicitationReviewRequest;
pub use elicitation::ElicitationReviewer;
pub use elicitation::ElicitationReviewerHandle;
pub use resource_client::McpResourceClient;
pub use resource_client::McpResourceClientCacheKey;
pub use resource_client::McpResourcePage;
pub use resource_client::McpResourceReadResult;
pub use rmcp_client::MCP_SANDBOX_STATE_META_CAPABILITY;
+18
View File
@@ -1,4 +1,5 @@
use std::sync::Arc;
use std::sync::Weak;
use anyhow::Context;
use anyhow::Result;
@@ -35,6 +36,18 @@ pub struct McpResourceClient {
manager: Arc<ArcSwap<McpConnectionManager>>,
}
/// Opaque identity for the manager currently used by an MCP resource client.
#[derive(Clone)]
pub struct McpResourceClientCacheKey(Weak<McpConnectionManager>);
impl PartialEq for McpResourceClientCacheKey {
fn eq(&self, other: &Self) -> bool {
self.0.ptr_eq(&other.0)
}
}
impl Eq for McpResourceClientCacheKey {}
impl std::fmt::Debug for McpResourceClient {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
@@ -49,6 +62,11 @@ impl McpResourceClient {
Self { manager }
}
/// Returns an identity that changes whenever the published manager changes.
pub fn cache_key(&self) -> McpResourceClientCacheKey {
McpResourceClientCacheKey(Arc::downgrade(&self.manager.load_full()))
}
/// Returns whether the current manager contains the named server.
///
/// This does not wait for server startup or imply that startup succeeded.