feat(mcp): synchronise les permissions MCP avec les tools réellement exposés
Les permissions déclarées ne reflétaient pas toujours les tools effectivement exposés aux agents (contexte requester/projet lié après coup). Ajoute ToolInvoker::tools_for_bound_context/tools_for_context pour exposer la liste effective au moment de l'injection dans une requête OpenAI-compatible, et propage le calcul dans la policy MCP, le serveur, la factory de session et l'adapter openai_compat. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@ -41,6 +41,12 @@ impl ToolInvoker for ProjectScopedToolInvoker {
|
||||
self.inner.tools()
|
||||
}
|
||||
|
||||
async fn tools_for_bound_context(&self) -> Result<Vec<ToolSpec>, ToolInvocationError> {
|
||||
self.inner
|
||||
.tools_for_context(&self.project_root, &self.requester)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn call(&self, name: &str, args_json: &str) -> Result<String, ToolInvocationError> {
|
||||
let mut value: Value = serde_json::from_str(args_json)
|
||||
.map_err(|e| ToolInvocationError::InvalidArguments(format!("JSON invalide: {e}")))?;
|
||||
@ -257,6 +263,7 @@ mod tests {
|
||||
#[derive(Default)]
|
||||
struct RecordingToolInvoker {
|
||||
call: Mutex<Option<(String, String)>>,
|
||||
tools_context: Mutex<Option<(String, String)>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@ -265,6 +272,20 @@ mod tests {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
async fn tools_for_context(
|
||||
&self,
|
||||
project_root: &str,
|
||||
requester: &str,
|
||||
) -> Result<Vec<ToolSpec>, ToolInvocationError> {
|
||||
*self.tools_context.lock().unwrap() =
|
||||
Some((project_root.to_owned(), requester.to_owned()));
|
||||
Ok(vec![ToolSpec {
|
||||
name: "idea_memory_read".to_owned(),
|
||||
description: "Read memory".to_owned(),
|
||||
input_schema: json!({"type":"object"}),
|
||||
}])
|
||||
}
|
||||
|
||||
async fn call(&self, name: &str, args_json: &str) -> Result<String, ToolInvocationError> {
|
||||
*self.call.lock().unwrap() = Some((name.to_owned(), args_json.to_owned()));
|
||||
Ok("ok".to_owned())
|
||||
@ -299,6 +320,28 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn project_scoped_tool_invoker_filters_tools_with_bound_context() {
|
||||
let recorder = Arc::new(RecordingToolInvoker::default());
|
||||
let invoker = ProjectScopedToolInvoker {
|
||||
inner: recorder.clone(),
|
||||
project_root: "/project/root".to_owned(),
|
||||
requester: "agent-1".to_owned(),
|
||||
};
|
||||
|
||||
let tools = invoker
|
||||
.tools_for_bound_context()
|
||||
.await
|
||||
.expect("tools resolve");
|
||||
|
||||
assert_eq!(tools.len(), 1);
|
||||
assert_eq!(tools[0].name, "idea_memory_read");
|
||||
assert_eq!(
|
||||
recorder.tools_context.lock().unwrap().clone(),
|
||||
Some(("/project/root".to_owned(), "agent-1".to_owned()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fallback_requester_uses_cwd_file_name_only_when_explicit_identity_absent() {
|
||||
assert_eq!(fallback_requester_from_cwd("/tmp/run/7"), "7");
|
||||
|
||||
@ -281,7 +281,7 @@ impl OpenAiCompatibleSession {
|
||||
send_tap(&tap, &ReplyEvent::Heartbeat);
|
||||
for iteration in 0..=self.config.effective_max_tool_iterations() {
|
||||
let transcript = self.transcript.lock().expect("mutex sain").clone();
|
||||
let tools = self.effective_tools();
|
||||
let tools = self.effective_tools().await?;
|
||||
let response = self.post_chat(&transcript, &tools, true).await;
|
||||
let response = match response {
|
||||
Ok(response) => response,
|
||||
@ -330,13 +330,17 @@ impl OpenAiCompatibleSession {
|
||||
unreachable!("loop returns at max_tool_iterations");
|
||||
}
|
||||
|
||||
fn effective_tools(&self) -> Vec<ToolSpec> {
|
||||
async fn effective_tools(&self) -> Result<Vec<ToolSpec>, AgentSessionError> {
|
||||
if *self.tools_disabled.lock().expect("mutex sain") {
|
||||
return Vec::new();
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
match &self.tool_invoker {
|
||||
Some(invoker) => invoker
|
||||
.tools_for_bound_context()
|
||||
.await
|
||||
.map_err(|e| AgentSessionError::Start(format!("résolution tools: {e}"))),
|
||||
None => Ok(Vec::new()),
|
||||
}
|
||||
self.tool_invoker
|
||||
.as_ref()
|
||||
.map_or_else(Vec::new, |invoker| invoker.tools())
|
||||
}
|
||||
|
||||
async fn post_chat(
|
||||
|
||||
Reference in New Issue
Block a user