Skip to main content

codetether_agent/plugin_marketplace/
hot_load.rs

1//! Hot-load MCP plugin servers at runtime.
2
3use anyhow::Result;
4use std::path::Path;
5use std::process::Command;
6
7/// A hot-loaded plugin instance.
8#[derive(Debug)]
9pub struct LoadedPlugin {
10    pub name: String,
11    pub pid: u32,
12    pub endpoint: String,
13    pub capabilities: Vec<String>,
14}
15
16/// Start a plugin MCP server subprocess.
17pub fn start_plugin(name: &str, binary_path: &Path, port: u16) -> Result<LoadedPlugin> {
18    let child = Command::new(binary_path)
19        .env("MCP_PORT", port.to_string())
20        .env("PLUGIN_NAME", name)
21        .spawn()?;
22    let pid = child.id();
23    tracing::info!(name, pid, port, "Plugin MCP server started");
24    Ok(LoadedPlugin {
25        name: name.to_string(),
26        pid,
27        endpoint: format!("http://127.0.0.1:{port}"),
28        capabilities: Vec::new(),
29    })
30}
31
32/// Stop a running plugin.
33pub fn stop_plugin(plugin: &LoadedPlugin) -> Result<()> {
34    tracing::info!(name = %plugin.name, pid = %plugin.pid, "Stopping plugin");
35    #[cfg(unix)]
36    {
37        if plugin.pid > i32::MAX as u32 {
38            anyhow::bail!("Invalid PID: {}", plugin.pid);
39        }
40        let ret = unsafe { libc::kill(plugin.pid as i32, libc::SIGTERM) };
41        if ret != 0 {
42            anyhow::bail!(
43                "Failed to send SIGTERM to plugin {}: errno {}",
44                plugin.name,
45                ret
46            );
47        }
48    }
49    Ok(())
50}