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:
@ -347,25 +347,20 @@ impl McpServer {
|
||||
/// The `tools/list` result: the catalogue as MCP tool descriptors.
|
||||
async fn tools_list_result(&self) -> Result<Value, JsonRpcError> {
|
||||
let ephemeral_policy = self.ephemeral_tool_policy();
|
||||
let durable_policy = self.durable_tool_policy().await?;
|
||||
let tools: Vec<Value> = tools::catalogue()
|
||||
.into_iter()
|
||||
.filter(|t| {
|
||||
ephemeral_policy
|
||||
.as_ref()
|
||||
.map_or(true, |policy| policy.permits(t.name))
|
||||
&& durable_policy
|
||||
.as_ref()
|
||||
.map_or(true, |policy| policy.permits(t.name))
|
||||
})
|
||||
.map(|t| {
|
||||
json!({
|
||||
"name": t.name,
|
||||
"description": t.description,
|
||||
"inputSchema": t.input_schema,
|
||||
let surface = self
|
||||
.effective_tool_surface(ephemeral_policy.as_ref())
|
||||
.await?;
|
||||
let tools: Vec<Value> =
|
||||
super::policy::effective_tool_catalogue(ephemeral_policy.as_ref(), &surface)
|
||||
.into_iter()
|
||||
.map(|t| {
|
||||
json!({
|
||||
"name": t.name,
|
||||
"description": t.description,
|
||||
"inputSchema": t.input_schema,
|
||||
})
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
.collect();
|
||||
Ok(json!({ "tools": tools }))
|
||||
}
|
||||
|
||||
@ -376,53 +371,30 @@ impl McpServer {
|
||||
}
|
||||
|
||||
async fn durable_tool_policy(&self) -> Result<Option<McpToolPolicy>, JsonRpcError> {
|
||||
let Some(store) = &self.mcp_tool_permissions else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let known_tools = tools::classified_tool_names();
|
||||
let policy = if let Some(agent_id) = self.requester_agent_id() {
|
||||
let doc = store
|
||||
.load_mcp_tool_permissions(&self.project)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
JsonRpcError::new(
|
||||
error_codes::INTERNAL_ERROR,
|
||||
format!("failed to load MCP tool permissions: {e}"),
|
||||
)
|
||||
})?;
|
||||
doc.effective_policy(agent_id, tools::READ_ONLY_TOOLS, &known_tools)
|
||||
.map_err(|e| {
|
||||
JsonRpcError::new(
|
||||
error_codes::INTERNAL_ERROR,
|
||||
format!("invalid MCP tool permissions: {e}"),
|
||||
)
|
||||
})?
|
||||
} else if self.requester.is_empty()
|
||||
|| self.requester == "mcp"
|
||||
|| self.ephemeral_tool_policy().is_none()
|
||||
{
|
||||
// Anonymous/legacy peers cannot be mapped to an agent override. Fail
|
||||
// closed to the canonical read-only policy. Non-agent requesters with an
|
||||
// ephemeral policy (ticket assistants) are governed by that narrower,
|
||||
// session-scoped policy instead of the durable per-agent store.
|
||||
McpToolPolicy::read_only(tools::READ_ONLY_TOOLS, &known_tools).map_err(|e| {
|
||||
JsonRpcError::new(
|
||||
error_codes::INTERNAL_ERROR,
|
||||
format!("invalid read-only MCP tool fallback: {e}"),
|
||||
)
|
||||
})?
|
||||
} else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
Ok(Some(policy))
|
||||
let ephemeral_policy = self.ephemeral_tool_policy();
|
||||
Ok(self
|
||||
.effective_tool_surface(ephemeral_policy.as_ref())
|
||||
.await?
|
||||
.durable_policy)
|
||||
}
|
||||
|
||||
fn requester_agent_id(&self) -> Option<AgentId> {
|
||||
uuid::Uuid::parse_str(&self.requester)
|
||||
.ok()
|
||||
.map(AgentId::from_uuid)
|
||||
async fn effective_tool_surface(
|
||||
&self,
|
||||
ephemeral_policy: Option<&AgentToolPolicy>,
|
||||
) -> Result<super::policy::EffectiveToolSurface, JsonRpcError> {
|
||||
super::policy::resolve_effective_tool_surface(
|
||||
self.mcp_tool_permissions.as_deref(),
|
||||
&self.project,
|
||||
&self.requester,
|
||||
ephemeral_policy,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
JsonRpcError::new(
|
||||
error_codes::INTERNAL_ERROR,
|
||||
format!("failed to resolve MCP tool permissions: {e}"),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn enforce_durable_tool_policy(
|
||||
|
||||
Reference in New Issue
Block a user