Skip to main content

objectiveai_cli/command/mcp/
spawn.rs

1//! `mcp spawn` — start the `objectiveai-mcp` server in the background,
2//! using `mcp.address` + `mcp.port` from on-disk config (mcp is the
3//! one tier that kept a configurable bind address).
4//!
5//! The mcp server is per-state: its lock lives at
6//! `<dir>/state/<state>/locks` key `mcp`, and the lock contents are
7//! the server's client-connect URL. If the lock is already held the
8//! server is already up and its published URL is returned as-is.
9
10use objectiveai_sdk::cli::command::mcp::spawn::{Request, Response};
11
12use crate::context::Context;
13use crate::error::Error;
14
15pub async fn execute(ctx: &Context, _request: Request) -> Result<Response, Error> {
16    let mut config = ctx
17        .filesystem
18        .read_config_view(objectiveai_sdk::cli::command::GetScope::Final)
19        .await?;
20    let address = config.mcp().get_address().map(String::from);
21    let port = config.mcp().get_port();
22
23    let bin = if cfg!(windows) {
24        "objectiveai-mcp.exe"
25    } else {
26        "objectiveai-mcp"
27    };
28    let exe = ctx.filesystem.bin_dir().join(bin);
29    let lock_dir = ctx.filesystem.state_dir().join("locks");
30
31    // The child inherits the cli's environment; every env key the mcp
32    // server's config reads (`EnvConfigBuilder` in
33    // `objectiveai-mcp/src/run.rs`: ADDRESS, PORT, SUPPRESS_OUTPUT,
34    // OBJECTIVEAI_DIR, OBJECTIVEAI_STATE) is either set explicitly
35    // here or scrubbed, so the spawning shell's settings can't leak
36    // in. ADDRESS/PORT come from on-disk config, each scrubbed when
37    // unset so the server falls back to its own defaults.
38    let listening = crate::spawn::spawn_until_lock_published(&exe, &lock_dir, "mcp", |cmd| {
39        cmd.env_remove("ADDRESS");
40        cmd.env_remove("PORT");
41        cmd.env("OBJECTIVEAI_DIR", ctx.filesystem.dir())
42            .env("OBJECTIVEAI_STATE", ctx.filesystem.state())
43            .env("SUPPRESS_OUTPUT", "true");
44        if let Some(address) = address {
45            cmd.env("ADDRESS", address);
46        }
47        if let Some(port) = port {
48            cmd.env("PORT", port.to_string());
49        }
50    })
51    .await?;
52    Ok(Response { listening })
53}
54
55pub mod request_schema {
56    use objectiveai_sdk::cli::command::mcp::spawn as sdk;
57    use objectiveai_sdk::cli::command::mcp::spawn::request_schema::{Request, Response};
58
59    use crate::context::Context;
60    use crate::error::Error;
61
62    pub async fn execute(_ctx: &Context, _request: Request) -> Result<Response, Error> {
63        Ok(objectiveai_sdk::cli::command::ResponseSchema(schemars::schema_for!(sdk::Request)))
64    }
65}
66
67pub mod response_schema {
68    use objectiveai_sdk::cli::command::mcp::spawn as sdk;
69    use objectiveai_sdk::cli::command::mcp::spawn::response_schema::{Request, Response};
70
71    use crate::context::Context;
72    use crate::error::Error;
73
74    pub async fn execute(_ctx: &Context, _request: Request) -> Result<Response, Error> {
75        Ok(objectiveai_sdk::cli::command::ResponseSchema(schemars::schema_for!(sdk::Response)))
76    }
77}