Files
codex/codex-rs/core/src/session/mcp_refresh.rs
T
sayan-oai 4213b38f3c Honor attachment-owned permissions for MCP servers (#40728)
## Why

MCP servers attached to executor environments must retain their owner's
permission profile instead of inheriting the thread-wide sandbox authority.

## What changed

- Resolve and capture a permission profile for each enabled MCP server when
  publishing the runtime, and reject calls or elicitations when that authority
  is unavailable.
- Use the captured server profile for tool approval decisions, elicitation, and
  sandbox metadata, including after runtime refreshes.
- Materialize `:workspace_roots` from `PathUri` values so permissions preserve
  the path convention of remote executor environments.
- Give threadless app discovery and resource reads an explicit default
  permission profile.

## Testing

Added coverage for per-server elicitation authority, unresolved attachments,
runtime refreshes, restricted tool calls, and foreign-platform workspace roots.

GitOrigin-RevId: 6b188d4b08b29e9971ff7aa68a7785a0cdf9e394
2026-08-25 22:50:49 +00:00

57 lines
1.4 KiB
Rust

use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use tokio::sync::AcquireError;
use tokio::sync::Semaphore;
use tokio::sync::SemaphorePermit;
/// Owns MCP invalidation and the single gate used to publish runtime updates.
pub(super) struct McpRefresh {
pending: AtomicBool,
gate: Semaphore,
}
impl McpRefresh {
pub(super) fn new() -> Self {
Self {
pending: AtomicBool::new(false),
gate: Semaphore::new(/*permits*/ 1),
}
}
pub(super) fn invalidate(&self) {
self.pending.store(true, Ordering::Release);
}
#[cfg(test)]
pub(super) fn is_pending(&self) -> bool {
self.pending.load(Ordering::Acquire)
}
pub(super) fn claim(&self) -> bool {
self.pending.swap(false, Ordering::AcqRel)
}
#[tracing::instrument(name = "mcp.runtime.refresh_wait", skip_all)]
pub(super) async fn acquire(&self) -> Result<SemaphorePermit<'_>, AcquireError> {
self.gate.acquire().await
}
pub(super) fn close(&self) {
self.gate.close();
}
}
/// Restores a claimed refresh when its task is cancelled before publication.
pub(super) struct McpRefreshInvalidationGuard<'a> {
pub(super) refresh: &'a McpRefresh,
pub(super) published: bool,
}
impl Drop for McpRefreshInvalidationGuard<'_> {
fn drop(&mut self) {
if !self.published {
self.refresh.invalidate();
}
}
}