Skip to main content

vtcode_mcp/
sandbox_context.rs

1//! Shared sandbox context for MCP stdio launches.
2
3use anyhow::{Result, anyhow};
4use hashbrown::HashMap;
5use std::ffi::OsString;
6use std::path::{Path, PathBuf};
7
8use vtcode_safety::sandboxing::{CommandSpec, LinuxSandboxLauncher, SandboxManager, SandboxPolicy};
9
10/// Sandbox policy and launch context inherited by MCP stdio providers.
11#[derive(Debug, Clone)]
12pub struct McpSandboxContext {
13    policy: SandboxPolicy,
14    sandbox_cwd: PathBuf,
15    linux_sandbox_executable: Option<LinuxSandboxLauncher>,
16}
17
18impl McpSandboxContext {
19    /// Create a context using the configured workspace as the sandbox cwd.
20    #[must_use]
21    pub fn new(policy: SandboxPolicy, sandbox_cwd: impl Into<PathBuf>) -> Self {
22        Self {
23            policy,
24            sandbox_cwd: sandbox_cwd.into(),
25            linux_sandbox_executable: LinuxSandboxLauncher::resolve(),
26        }
27    }
28
29    /// Override the Linux helper path, primarily for embedding applications and tests.
30    #[must_use]
31    pub fn with_linux_sandbox_executable(mut self, executable: impl Into<PathBuf>) -> Self {
32        self.linux_sandbox_executable = Some(LinuxSandboxLauncher::external(executable.into()));
33        self
34    }
35
36    /// Return the policy carried by this context.
37    #[must_use]
38    pub fn policy(&self) -> &SandboxPolicy {
39        &self.policy
40    }
41
42    pub(crate) fn transform_stdio(
43        &self,
44        program: OsString,
45        args: Vec<OsString>,
46        working_dir: Option<&Path>,
47        env: HashMap<OsString, OsString>,
48    ) -> Result<SandboxedStdioCommand> {
49        if matches!(self.policy, SandboxPolicy::ExternalSandbox { .. }) {
50            return Err(anyhow!("MCP stdio cannot use an external sandbox policy without an external launcher"));
51        }
52
53        let cwd = working_dir.map(Path::to_path_buf).unwrap_or_else(|| self.sandbox_cwd.clone());
54        let spec = CommandSpec::new(program.clone())
55            .with_args(args.iter().map(|arg| arg.to_string_lossy().into_owned()))
56            .with_cwd(cwd.clone())
57            .with_env(
58                env.iter()
59                    .map(|(key, value)| (key.to_string_lossy().into_owned(), value.to_string_lossy().into_owned()))
60                    .collect(),
61            );
62        let exec_env = SandboxManager::new()
63            .transform(spec, &self.policy, &cwd, self.linux_sandbox_executable.as_ref())
64            .map_err(|error| anyhow!("failed to apply MCP sandbox: {error}"))?;
65
66        Ok(SandboxedStdioCommand {
67            program: exec_env.program.into_os_string(),
68            args: exec_env.args.into_iter().map(OsString::from).collect(),
69            working_dir: exec_env.cwd,
70            env: exec_env
71                .env
72                .into_iter()
73                .map(|(key, value)| (OsString::from(key), OsString::from(value)))
74                .collect(),
75        })
76    }
77}
78
79pub(crate) struct SandboxedStdioCommand {
80    pub(crate) program: OsString,
81    pub(crate) args: Vec<OsString>,
82    pub(crate) working_dir: PathBuf,
83    pub(crate) env: HashMap<OsString, OsString>,
84}