Skip to content
This repository was archived by the owner on Jul 24, 2026. It is now read-only.
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
12 changes: 12 additions & 0 deletions crates/mcp_backend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ pub struct SubmitRequest {
pub config: ServerConfig,
/// `true` = register (admin/owner, auto-approved); `false` = request review.
pub register: bool,
/// The machine the server was discovered on. The backend uses this to scope
/// approval of a local (stdio) server to the specific host it lives on.
pub hostname: String,
}

/// Async client bound to a base URL + bearer key.
Expand Down Expand Up @@ -390,10 +393,14 @@ fn submit_body(req: &SubmitRequest) -> serde_json::Value {
let mut body = json!({
"name": req.name,
"status": if req.register { "registered" } else { "requested" },
"hostname": req.hostname,
});
let obj = body.as_object_mut().expect("object literal");
match &req.config {
ServerConfig::Stdio { command, args, env } => {
// Explicit transport type so the backend doesn't have to infer stdio
// from the presence of `command`.
obj.insert("type".into(), json!("stdio"));
obj.insert("command".into(), json!(command));
obj.insert("args".into(), json!(args));
obj.insert("env".into(), json!(env));
Expand Down Expand Up @@ -473,11 +480,14 @@ mod tests {
env: BTreeMap::new(),
},
register: true,
hostname: "dev-box".into(),
};
let b = submit_body(&stdio);
assert_eq!(b["name"], "s");
assert_eq!(b["status"], "registered");
assert_eq!(b["type"], "stdio");
assert_eq!(b["command"], "npx");
assert_eq!(b["hostname"], "dev-box");

let http = SubmitRequest {
name: "h".into(),
Expand All @@ -487,11 +497,13 @@ mod tests {
kind: HttpKind::Sse,
},
register: false,
hostname: "dev-box".into(),
};
let b = submit_body(&http);
assert_eq!(b["status"], "requested");
assert_eq!(b["type"], "sse");
assert_eq!(b["url"], "https://x");
assert_eq!(b["hostname"], "dev-box");
}

#[test]
Expand Down
1 change: 1 addition & 0 deletions crates/mcp_detector_daemon/src/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,7 @@ async fn submit_to_ew(
name: name.to_string(),
config,
register,
hostname: crate::platform::hostname(),
})
.await;
match res {
Expand Down
30 changes: 30 additions & 0 deletions crates/mcp_detector_daemon/src/platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,33 @@ mod imp {
}

pub use imp::*;

/// Best-effort machine hostname, sent to the backend so a local (stdio) server
/// can be approved for the specific host it lives on.
///
/// IMPORTANT: this must stay aligned with edison-stdiod's `config::hostname()`
/// (env `HOSTNAME`, then `COMPUTERNAME`, then the `hostname` command) so the
/// backend keys the *same* machine identity for both daemons. The command
/// fallback is what works on macOS, where `HOSTNAME` isn't exported to
/// launchd/user processes. On Windows `COMPUTERNAME` is always set and
/// short-circuits, so the GUI-subsystem daemon never spawns a console
/// `hostname`. Cross-platform, so it lives outside the cfg-split `imp`.
pub fn hostname() -> String {
for var in ["HOSTNAME", "COMPUTERNAME"] {
if let Ok(h) = std::env::var(var) {
let trimmed = h.trim();
if !trimmed.is_empty() {
return trimmed.to_string();
}
}
}
if let Ok(out) = std::process::Command::new("hostname").output()
&& let Ok(s) = String::from_utf8(out.stdout)
{
let trimmed = s.trim();
if !trimmed.is_empty() {
return trimmed.to_string();
}
}
"unknown".to_string()
}
Loading