Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Packaged macOS and Linux builds expect these tools to be available on `PATH` for

Desktop-launched apps on macOS/Linux may not inherit your shell dotfiles. First-run onboarding shows a system check for required tools. If a CLI is not detected, set its path in Settings > Agents & Models.

The local HTTP API used by external automation is disabled by default in packaged apps. Start KaitenCode with `KAITENCODE_LOCAL_API=1` only when you intentionally want a local MCP/automation client to control the running app; the app writes a user-readable-only JSON discovery file with the selected port and a per-run bearer token to `~/.kaitencode/api.port` while enabled.
The local HTTP API powers the MCP server and any external automation that drives the board, so it is **enabled by default**. It binds to `127.0.0.1` only and authenticates every request with a per-run bearer token, written along with the chosen port to a user-readable-only (`0600`) discovery file at `~/.kaitencode/api.port`. To opt out (no MCP/automation control), start KaitenCode with `KAITENCODE_LOCAL_API=0` (or `false`/`no`/`off`).

### Building

Expand Down
38 changes: 27 additions & 11 deletions src-tauri/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -868,7 +868,7 @@ fn write_api_discovery_file(path: &std::path::Path, contents: &str) -> std::io::
pub fn start(app: AppHandle) {
if !local_api_enabled() {
let _ = std::fs::remove_file(port_file_path());
log::info!("[api] Local HTTP API disabled; set KAITENCODE_LOCAL_API=1 to enable");
log::info!("[api] Local HTTP API disabled via KAITENCODE_LOCAL_API; MCP/automation clients won't be able to reach the app");
return;
}

Expand Down Expand Up @@ -967,15 +967,20 @@ pub fn cleanup() {
let _ = std::fs::remove_file(port_file_path());
}

/// Whether to start the local HTTP API. Defaults to **ON**: the MCP server and
/// Claude Code drive the board through it, so off-by-default silently breaks
/// every MCP mutation. The server binds `127.0.0.1` only and authenticates with
/// a per-process bearer token written to a `0600` discovery file, so enabling it
/// by default is local-only and safe. Set `KAITENCODE_LOCAL_API=0` (or
/// `false`/`no`/`off`) to opt out.
fn local_api_enabled() -> bool {
std::env::var("KAITENCODE_LOCAL_API")
.map(|value| {
matches!(
value.trim().to_ascii_lowercase().as_str(),
"1" | "true" | "yes" | "on"
)
})
.unwrap_or(false)
match std::env::var("KAITENCODE_LOCAL_API") {
Ok(value) => !matches!(
value.trim().to_ascii_lowercase().as_str(),
"0" | "false" | "no" | "off"
),
Err(_) => true,
}
}

#[cfg(test)]
Expand Down Expand Up @@ -1010,10 +1015,11 @@ mod tests {
}

#[test]
fn local_api_disabled_by_default() {
fn local_api_enabled_by_default() {
let _guard = ENV_LOCK.lock().expect("env lock");
std::env::remove_var("KAITENCODE_LOCAL_API");
assert!(!local_api_enabled());
// On by default — MCP depends on it.
assert!(local_api_enabled());
}

#[test]
Expand All @@ -1025,4 +1031,14 @@ mod tests {
}
std::env::remove_var("KAITENCODE_LOCAL_API");
}

#[test]
fn local_api_respects_explicit_disable_values() {
let _guard = ENV_LOCK.lock().expect("env lock");
for value in ["0", "false", "no", "off", "OFF", " false "] {
std::env::set_var("KAITENCODE_LOCAL_API", value);
assert!(!local_api_enabled(), "value {value:?}");
}
std::env::remove_var("KAITENCODE_LOCAL_API");
}
}
Loading