Dynamically refresh skill instructions each turn (#9217)

Signed-off-by: Douwe Osinga <douwe@squareup.com>
Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
Douwe Osinga
2026-05-14 16:00:04 -04:00
committed by GitHub
parent cac334b30f
commit 401f8e86ba
5 changed files with 36 additions and 32 deletions
@@ -56,7 +56,7 @@ impl McpClientTrait for MockClient {
} }
fn get_info(&self) -> std::option::Option<&rmcp::model::InitializeResult> { fn get_info(&self) -> std::option::Option<&rmcp::model::InitializeResult> {
todo!() None
} }
async fn read_resource( async fn read_resource(
+1 -3
View File
@@ -106,9 +106,7 @@ impl Extension {
} }
fn get_instructions(&self) -> Option<String> { fn get_instructions(&self) -> Option<String> {
self.server_info self.client.get_instructions()
.as_ref()
.and_then(|info| info.instructions.clone())
} }
fn get_client(&self) -> McpClientBox { fn get_client(&self) -> McpClientBox {
+7
View File
@@ -84,6 +84,13 @@ pub trait McpClientTrait: Send + Sync {
fn get_info(&self) -> Option<&InitializeResult>; fn get_info(&self) -> Option<&InitializeResult>;
/// Return the extension's current instructions. The default reads from
/// `get_info()`, but platform extensions can override this to provide
/// dynamically computed instructions (e.g. freshly discovered skills).
fn get_instructions(&self) -> Option<String> {
self.get_info().and_then(|info| info.instructions.clone())
}
async fn list_resources( async fn list_resources(
&self, &self,
_session_id: &str, _session_id: &str,
+3 -5
View File
@@ -441,11 +441,9 @@ mod tests {
.values() .values()
.map(|def| { .map(|def| {
let client = (def.client_factory)(context.clone()); let client = (def.client_factory)(context.clone());
let info = client.get_info(); let instructions = client.get_instructions().unwrap_or_default();
let instructions = info let has_resources = client
.and_then(|i| i.instructions.clone()) .get_info()
.unwrap_or_default();
let has_resources = info
.and_then(|i| i.capabilities.resources.as_ref()) .and_then(|i| i.capabilities.resources.as_ref())
.is_some(); .is_some();
ExtensionInfo::new(def.name, &instructions, has_resources) ExtensionInfo::new(def.name, &instructions, has_resources)
+24 -23
View File
@@ -27,30 +27,8 @@ impl SkillsClient {
.map(|s| s.working_dir.clone()) .map(|s| s.working_dir.clone())
.unwrap_or_else(|| std::env::current_dir().unwrap_or_default()); .unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
let mut instructions = String::new();
if context.session.is_some() {
let sources = discover_skills(Some(&working_dir));
let mut skills: Vec<&SourceEntry> = sources
.iter()
.filter(|s| {
s.source_type == SourceType::Skill || s.source_type == SourceType::BuiltinSkill
})
.collect();
skills.sort_by(|a, b| (&a.name, &a.path).cmp(&(&b.name, &b.path)));
if !skills.is_empty() {
instructions.push_str(
"\n\nYou have these skills at your disposal, when it is clear they can help you solve a problem or you are asked to use them:",
);
for skill in &skills {
instructions.push_str(&format!("\n{} - {}", skill.name, skill.description));
}
}
}
let info = InitializeResult::new(ServerCapabilities::builder().enable_tools().build()) let info = InitializeResult::new(ServerCapabilities::builder().enable_tools().build())
.with_server_info(Implementation::new(EXTENSION_NAME, "1.0.0").with_title("Skills")) .with_server_info(Implementation::new(EXTENSION_NAME, "1.0.0").with_title("Skills"));
.with_instructions(instructions);
Ok(Self { info, working_dir }) Ok(Self { info, working_dir })
} }
@@ -252,6 +230,29 @@ impl McpClientTrait for SkillsClient {
Some(&self.info) Some(&self.info)
} }
fn get_instructions(&self) -> Option<String> {
let sources = discover_skills(Some(&self.working_dir));
let mut skills: Vec<&SourceEntry> = sources
.iter()
.filter(|s| {
s.source_type == SourceType::Skill || s.source_type == SourceType::BuiltinSkill
})
.collect();
skills.sort_by(|a, b| (&a.name, &a.path).cmp(&(&b.name, &b.path)));
if skills.is_empty() {
return None;
}
let mut instructions = String::from(
"\n\nYou have these skills at your disposal, when it is clear they can help you solve a problem or you are asked to use them:",
);
for skill in &skills {
instructions.push_str(&format!("\n{} - {}", skill.name, skill.description));
}
Some(instructions)
}
async fn subscribe(&self) -> mpsc::Receiver<ServerNotification> { async fn subscribe(&self) -> mpsc::Receiver<ServerNotification> {
let (_tx, rx) = mpsc::channel(1); let (_tx, rx) = mpsc::channel(1);
rx rx