use std::sync::Arc;
use std::time::Duration;
use mcpmesh::allowlist::{AllowlistGate, PeerEntry, PeerStore};
use mcpmesh::config::Config;
use mcpmesh::daemon::{
EphemeralService, MeshState, build_services, grant_service_access, grant_service_allow,
revoke_service_allow, spawn_accept_loop,
};
use mcpmesh::pairing::LiveInvites;
use mcpmesh::roster::gate::RosterGate;
use mcpmesh_net::registry::ConnRegistry;
use mcpmesh_net::{ALPN_MCP, MAX_FRAME_BYTES, SessionTransport, TrustGate, framing::write_frame};
use serde_json::json;
use tokio::time::timeout;
const STUB: &str = env!("CARGO_BIN_EXE_echo_mcp_stub");
async fn server_endpoint() -> iroh::Endpoint {
iroh::Endpoint::builder(iroh::endpoint::presets::Minimal)
.relay_mode(iroh::RelayMode::Disabled)
.alpns(vec![ALPN_MCP.to_vec()])
.bind()
.await
.expect("bind server endpoint")
}
async fn client_endpoint() -> iroh::Endpoint {
iroh::Endpoint::builder(iroh::endpoint::presets::Minimal)
.relay_mode(iroh::RelayMode::Disabled)
.alpns(vec![ALPN_MCP.to_vec()])
.bind()
.await
.expect("bind client endpoint")
}
fn initialize_frame(service: &str) -> serde_json::Value {
json!({
"jsonrpc": "2.0", "id": 1, "method": "initialize",
"params": {
"protocolVersion": "2025-11-25",
"_meta": {"mcpmesh/service": service},
"capabilities": {}, "clientInfo": {"name": "tester", "version": "0"}
}
})
}
async fn mesh_with_ephemeral_room() -> (
Arc<MeshState>,
iroh::EndpointAddr,
iroh::Endpoint,
String,
tempfile::TempDir,
) {
let dir = tempfile::tempdir().unwrap();
let store = Arc::new(PeerStore::open(&dir.path().join("state.redb")).unwrap());
let peer = client_endpoint().await;
let principal = format!("eid:{}", peer.id());
store
.add(PeerEntry {
endpoint_id: *peer.id().as_bytes(),
nickname: "alice".into(),
services: vec!["room".into()],
paired_at: None,
user_id: None,
last_addr: None,
})
.unwrap();
let config_path = dir.path().join("config.toml");
let toml = format!("[services.kept]\nrun = ['{STUB}']\nallow = [\"{principal}\"]\n");
std::fs::write(&config_path, &toml).unwrap();
let cfg = Config::from_toml_str(&toml).expect("parse config");
let gate: Arc<dyn TrustGate> = Arc::new(AllowlistGate::new(store.clone()));
let conn_registry = Arc::new(ConnRegistry::new());
let server = server_endpoint().await;
let addr = server.addr();
let mesh = MeshState::new(
server,
gate,
store,
Arc::new(LiveInvites::new()),
"server".into(),
config_path,
Arc::new(RosterGate::empty()),
conn_registry,
None,
None,
None,
None,
);
mesh.register_ephemeral(
"room".to_string(),
EphemeralService {
backend: mcpmesh_local_api::BackendSpec::Run {
cmd: vec![STUB.to_string()],
env: Default::default(),
cwd: None,
},
allow: vec![],
},
);
mesh.set_accept_task(spawn_accept_loop(
mesh.clone(),
Arc::new(build_services(&cfg)),
))
.await;
(mesh, addr, peer, principal, dir)
}
async fn dial(client: &iroh::Endpoint, addr: iroh::EndpointAddr) -> iroh::endpoint::Connection {
client.connect(addr, ALPN_MCP).await.expect("dial mesh")
}
async fn open_session(
conn: &iroh::endpoint::Connection,
service: &str,
) -> Option<SessionTransport> {
let (mut send, recv) = conn.open_bi().await.ok()?;
write_frame(&mut send, &initialize_frame(service))
.await
.ok()?;
Some(SessionTransport::new(recv, send, MAX_FRAME_BYTES))
}
async fn session_served(t: Option<&mut SessionTransport>) -> bool {
let Some(t) = t else { return false };
match timeout(Duration::from_secs(5), t.recv_value()).await {
Ok(Ok(Some(v))) => v["result"]["serverInfo"]["name"] == "echo-stub",
_ => false,
}
}
#[tokio::test]
async fn granting_an_ephemeral_service_admits_the_peer() {
timeout(Duration::from_secs(60), async {
let (mesh, addr, peer, principal, _dir) = mesh_with_ephemeral_room().await;
let conn = dial(&peer, addr).await;
let mut before = open_session(&conn, "room").await;
assert!(
!session_served(before.as_mut()).await,
"not served before the grant (setup) — the ephemeral service is not in the registry \
yet and admits nobody"
);
grant_service_access(&mesh, &principal, &principal, &["room".to_string()])
.await
.expect("grant succeeds");
let mut after = open_session(&conn, "room").await;
assert!(
session_served(after.as_mut()).await,
"a grant on an EPHEMERAL service must actually admit the peer — this returned success \
and admitted nobody before #55"
);
})
.await
.expect("ephemeral grant test timed out");
}
#[tokio::test]
async fn revoking_an_ephemeral_service_withdraws_access_and_severs() {
timeout(Duration::from_secs(60), async {
let (mesh, addr, peer, principal, _dir) = mesh_with_ephemeral_room().await;
grant_service_access(&mesh, &principal, &principal, &["room".to_string()])
.await
.expect("grant succeeds");
let conn = dial(&peer, addr.clone()).await;
let mut session = open_session(&conn, "room").await;
assert!(session_served(session.as_mut()).await, "served after grant");
revoke_service_allow(&mesh, "room".into(), principal.clone())
.await
.expect("revoke succeeds");
timeout(Duration::from_secs(5), conn.closed())
.await
.expect("an ephemeral revoke must sever the live connection");
let conn2 = dial(&peer, addr).await;
let mut after = open_session(&conn2, "room").await;
assert!(
!session_served(after.as_mut()).await,
"a revoked principal must not be re-admitted by the ephemeral overlay"
);
})
.await
.expect("ephemeral revoke test timed out");
}
#[tokio::test]
async fn the_pairing_grant_still_tolerates_an_unknown_service() {
timeout(Duration::from_secs(60), async {
let (mesh, addr, peer, principal, _dir) = mesh_with_ephemeral_room().await;
grant_service_access(
&mesh,
&principal,
&principal,
&["room".to_string(), "no-such-service".to_string()],
)
.await
.expect("a pairing grant must not fail on an unknown service name");
let conn = dial(&peer, addr).await;
let mut s = open_session(&conn, "room").await;
assert!(
session_served(s.as_mut()).await,
"the KNOWN service in the list must still have been granted"
);
})
.await
.expect("lenient pairing grant test timed out");
}
#[tokio::test]
async fn an_ephemeral_registration_survives_an_unrelated_swap() {
timeout(Duration::from_secs(60), async {
let (mesh, addr, peer, principal, _dir) = mesh_with_ephemeral_room().await;
grant_service_access(&mesh, &principal, &principal, &["room".to_string()])
.await
.expect("grant room");
grant_service_access(
&mesh,
"eid:0000000000000000000000000000000000000000000000000000000000000000",
"someone-else",
&["kept".to_string()],
)
.await
.expect("grant kept");
let conn = dial(&peer, addr).await;
let mut s = open_session(&conn, "room").await;
assert!(
session_served(s.as_mut()).await,
"the ephemeral service (and its granted allow) must survive an unrelated config swap"
);
})
.await
.expect("overlay survival test timed out");
}
#[tokio::test]
async fn a_revoke_strips_a_name_held_by_both_config_and_the_overlay() {
timeout(Duration::from_secs(90), async {
let (mesh, addr, peer, principal, dir) = mesh_with_ephemeral_room().await;
let config_path = dir.path().join("config.toml");
std::fs::write(
&config_path,
format!(
"[services.kept]\nrun = ['{STUB}']\nallow = [\"{principal}\"]\n\
[services.room]\nrun = ['{STUB}']\nallow = [\"{principal}\"]\n"
),
)
.unwrap();
grant_service_access(&mesh, &principal, "alice", &["room".to_string()])
.await
.expect("grant");
revoke_service_allow(&mesh, "room".to_string(), principal.clone())
.await
.expect("revoke");
let on_disk = std::fs::read_to_string(&config_path).expect("read config back");
let room_section = on_disk
.split("[services.room]")
.nth(1)
.expect("room section present");
assert!(
!room_section.contains(&principal),
"the revoke must strip the principal from the CONFIG copy too — config after \
revoke:\n{on_disk}"
);
mcpmesh::daemon::unregister_ephemeral(&mesh, &["room".to_string()]).await;
let live = mesh.live_services();
let room_allow = live.get("room").map(|e| e.allow.clone());
assert_eq!(
room_allow,
Some(Vec::<String>::new()),
"after dropping the overlay, the live `room` must be the CONFIG entry with an empty \
allow (got {room_allow:?})"
);
let conn = dial(&peer, addr).await;
let mut t = open_session(&conn, "room").await;
assert!(
!session_served(t.as_mut()).await,
"the config copy of a both-held name must not survive the revoke — this is #55's \
defect: a revoke that reported success, then re-admitted the peer the moment the \
ephemeral registration dropped"
);
})
.await
.expect("both-held revoke test timed out");
}
#[tokio::test]
async fn an_overlay_only_grant_does_not_apply_an_unrelated_config_edit() {
timeout(Duration::from_secs(90), async {
let (mesh, addr, peer, principal, dir) = mesh_with_ephemeral_room().await;
grant_service_allow(&mesh, "room".to_string(), "b64u:carol".to_string())
.await
.expect("first grant");
std::fs::write(
dir.path().join("config.toml"),
format!(
"[services.kept]\nrun = ['{STUB}']\nallow = [\"{principal}\"]\n\
[services.late]\nrun = ['{STUB}']\nallow = [\"{principal}\"]\n"
),
)
.unwrap();
grant_service_allow(&mesh, "room".to_string(), principal.clone())
.await
.expect("second grant");
let conn = dial(&peer, addr).await;
let mut room = open_session(&conn, "room").await;
assert!(
session_served(room.as_mut()).await,
"the targeted swap must reach the live registry — this grant never touched disk"
);
let mut late = open_session(&conn, "late").await;
assert!(
!session_served(late.as_mut()).await,
"an overlay-only grant must NOT hot-load an unrelated config service — the disk \
reload is deliberately skipped (#94)"
);
})
.await
.expect("unrelated config edit test timed out");
}
#[tokio::test]
async fn a_revoke_leaves_the_live_registry_denying_when_it_returns() {
timeout(Duration::from_secs(60), async {
let (mesh, addr, peer, principal, _dir) = mesh_with_ephemeral_room().await;
grant_service_access(&mesh, &principal, &principal, &["room".to_string()])
.await
.expect("grant");
let conn = dial(&peer, addr).await;
let mut session = open_session(&conn, "room").await;
assert!(session_served(session.as_mut()).await, "served after grant");
revoke_service_allow(&mesh, "room".into(), principal.clone())
.await
.expect("revoke");
let live = mesh.live_services();
let room_allow = live.get("room").map(|e| e.allow.clone());
assert_eq!(
room_allow,
Some(Vec::<String>::new()),
"the revoke must leave the live registry denying — a fast path that severed without \
installing the new registry would re-admit on the next dial (got {room_allow:?})"
);
})
.await
.expect("registry-denies-on-return test timed out");
}
#[tokio::test]
async fn the_registry_already_denies_at_the_moment_of_the_sever() {
timeout(Duration::from_secs(60), async {
let (mesh, addr, peer, principal, _dir) = mesh_with_ephemeral_room().await;
grant_service_access(&mesh, &principal, &principal, &["room".to_string()])
.await
.expect("grant");
let conn = dial(&peer, addr).await;
let mut session = open_session(&conn, "room").await;
assert!(session_served(session.as_mut()).await, "served after grant");
let seen: Arc<std::sync::Mutex<Vec<Vec<String>>>> = Arc::new(std::sync::Mutex::new(vec![]));
let sink = seen.clone();
mesh.set_sever_observer(move |live| {
sink.lock().expect("observer sink not poisoned").push(
live.get("room")
.map(|e| e.allow.clone())
.unwrap_or_default(),
);
});
revoke_service_allow(&mesh, "room".into(), principal.clone())
.await
.expect("revoke");
let observed = seen.lock().expect("observer sink not poisoned").clone();
assert!(
!observed.is_empty(),
"the revoke must have severed, firing the observer"
);
for at_sever in &observed {
assert!(
at_sever.is_empty(),
"the new registry must be installed BEFORE every sever — at one sever `room` \
still admitted {at_sever:?}, so a peer cut by that sever could have redialled \
straight back in (all observations: {observed:?})"
);
}
})
.await
.expect("swap-before-sever test timed out");
}
#[tokio::test]
async fn peer_services_does_not_report_a_config_service_pending_a_reload() {
timeout(Duration::from_secs(60), async {
let (mesh, _addr, peer, principal, dir) = mesh_with_ephemeral_room().await;
grant_service_allow(&mesh, "room".to_string(), principal.clone())
.await
.expect("first grant");
std::fs::write(
dir.path().join("config.toml"),
format!(
"[services.kept]\nrun = ['{STUB}']\nallow = [\"{principal}\"]\n\
[services.late]\nrun = ['{STUB}']\nallow = [\"{principal}\"]\n"
),
)
.unwrap();
grant_service_allow(&mesh, "room".to_string(), "b64u:carol".to_string())
.await
.expect("second grant");
let identity = mcpmesh_net::PeerIdentity {
endpoint: mcpmesh_net::EndpointId::from_bytes(*peer.id().as_bytes()),
name: "alice".into(),
user_id: None,
groups: vec![],
};
let admitted = mcpmesh::daemon::admitted_services_for_test(&mesh, &identity);
assert!(
admitted.contains(&"room".to_string()),
"the live ephemeral service must still be reported (got {admitted:?})"
);
assert!(
admitted.contains(&"kept".to_string()),
"a LIVE config service that admits the caller must still be reported (got {admitted:?})"
);
assert!(
!admitted.contains(&"late".to_string()),
"a config service that is not in the live registry must NOT be reported as usable — \
the accept path would refuse it, and the caller cannot tell that from a network \
failure (got {admitted:?})"
);
})
.await
.expect("peer_services live-registry test timed out");
}
#[tokio::test]
async fn status_hides_a_pending_service_but_an_invite_can_still_name_it() {
timeout(Duration::from_secs(60), async {
let (mesh, _addr, _peer, principal, dir) = mesh_with_ephemeral_room().await;
grant_service_allow(&mesh, "room".to_string(), principal.clone())
.await
.expect("first grant");
std::fs::write(
dir.path().join("config.toml"),
format!(
"[services.kept]\nrun = ['{STUB}']\nallow = [\"{principal}\"]\n\
[services.late]\nrun = ['{STUB}']\nallow = [\"{principal}\"]\n"
),
)
.unwrap();
grant_service_allow(&mesh, "room".to_string(), "b64u:carol".to_string())
.await
.expect("second grant");
let listed = mcpmesh::daemon::service_infos_for_test(&mesh);
let names: Vec<&str> = listed.iter().map(|s| s.name.as_str()).collect();
assert!(
!names.contains(&"late"),
"status must not list a config service absent from the live registry (got {names:?})"
);
assert!(
names.contains(&"room"),
"the live ephemeral service must still be listed (got {names:?})"
);
let room = listed.iter().find(|s| s.name == "room").expect("room");
assert!(
room.ephemeral,
"an ephemeral registration must report as such"
);
let kept = listed.iter().find(|s| s.name == "kept").expect("kept");
assert!(
!kept.ephemeral,
"a config service must not report as ephemeral"
);
mcpmesh::daemon::mint_invite_for_test(&mesh, &["late".to_string()])
.await
.expect(
"mint_invite must accept a config service pending a reload — an invite is \
redeemed later, after reloads",
);
mcpmesh::daemon::mint_invite_for_test(&mesh, &["nope".to_string()])
.await
.expect_err(
"mint_invite must still reject a service in neither config nor the overlay",
);
})
.await
.expect("status live-registry test timed out");
}
#[tokio::test]
async fn status_metadata_honours_overlay_precedence_and_the_socket_backend() {
timeout(Duration::from_secs(90), async {
let (mesh, _addr, _peer, principal, dir) = mesh_with_ephemeral_room().await;
std::fs::write(
dir.path().join("config.toml"),
format!(
"[services.kept]\nrun = ['{STUB}']\nallow = [\"{principal}\"]\n\
[services.room]\nsocket = \"/run/room.sock\"\nallow = [\"{principal}\"]\n\
[services.sock]\nsocket = \"/run/s.sock\"\nallow = [\"{principal}\"]\n"
),
)
.unwrap();
grant_service_allow(&mesh, "sock".to_string(), "b64u:carol".to_string())
.await
.expect("grant on the socket service");
let listed = mcpmesh::daemon::service_infos_for_test(&mesh);
let room = listed.iter().find(|s| s.name == "room").expect("room");
assert!(
room.ephemeral,
"a name held by BOTH sources must report the OVERLAY's entry — the overlay is what the \
accept path serves"
);
assert_eq!(
room.backend,
mcpmesh_local_api::BackendKind::Run,
"and the overlay's BACKEND SHAPE too — config declares this name as a socket, the \
ephemeral registration as run, and the registry holds the latter"
);
let sock = listed.iter().find(|s| s.name == "sock").expect("sock");
assert_eq!(
sock.backend,
mcpmesh_local_api::BackendKind::Socket,
"a socket-backed config service must report Socket, not Run"
);
assert!(!sock.ephemeral, "a config service is not ephemeral");
})
.await
.expect("status metadata test timed out");
}
#[tokio::test]
async fn a_pending_config_service_is_reported_once_it_is_actually_live() {
timeout(Duration::from_secs(90), async {
let (mesh, _addr, _peer, principal, dir) = mesh_with_ephemeral_room().await;
grant_service_allow(&mesh, "room".to_string(), principal.clone())
.await
.expect("first grant");
std::fs::write(
dir.path().join("config.toml"),
format!(
"[services.kept]\nrun = ['{STUB}']\nallow = [\"{principal}\"]\n\
[services.late]\nrun = ['{STUB}']\nallow = [\"{principal}\"]\n"
),
)
.unwrap();
grant_service_allow(&mesh, "room".to_string(), "b64u:carol".to_string())
.await
.expect("overlay-only grant");
let before = mcpmesh::daemon::service_infos_for_test(&mesh);
assert!(
!before.iter().any(|s| s.name == "late"),
"not live yet, so not reported"
);
grant_service_allow(&mesh, "late".to_string(), "b64u:dave".to_string())
.await
.expect("config grant reloads");
let after = mcpmesh::daemon::service_infos_for_test(&mesh);
assert!(
after.iter().any(|s| s.name == "late"),
"once the registry actually holds it, it MUST be reported — #100 withholds only what \
is not live, it does not stop reporting config services (got {:?})",
after.iter().map(|s| &s.name).collect::<Vec<_>>()
);
})
.await
.expect("pending-then-live test timed out");
}