use std::sync::Arc;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt};
use super::*;
struct EchoExtTool;
impl RustTool for EchoExtTool {
type Params = EmptyParams;
const NAME: &'static str = "echo_ext";
const DESCRIPTION: &'static str = "Returns '<caller>:<shared-label>'.";
async fn call(
&self,
_params: Self::Params,
ctx: &ToolContext,
) -> Result<ToolOutput, ToolError> {
let caller = ctx.conversation_id().unwrap_or("anonymous").to_owned();
let label = ctx
.get_ext::<Arc<String>>()
.map_or_else(|| "no-label".to_owned(), |l| (*l).clone());
Ok(ToolOutput::new(format!("{caller}:{label}")))
}
}
fn identity_server(per_connection: bool) -> McpServer {
let registry = ToolRegistry::new()
.with_tool(ContextTool)
.with_tool(EchoExtTool);
let ctx = ToolContext::new().with_conversation_id("server");
ctx.set_ext(Arc::new("sess42".to_owned()))
.expect("seed shared extension");
McpServer::new("identity-test", "0.0.1", registry)
.with_context(ctx)
.with_per_connection_identity(per_connection)
}
const INIT_ALICE: &str = r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","clientInfo":{"name":"alice","version":"1"}}}"#;
const INIT_BOB: &str = r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","clientInfo":{"name":"bob","version":"1"}}}"#;
const INIT_NO_NAME: &str =
r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05"}}"#;
const WHOAMI: &str =
r#"{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"whoami","arguments":{}}}"#;
const ECHO_EXT: &str =
r#"{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"echo_ext","arguments":{}}}"#;
async fn init_conn(server: &McpServer, conn: &mut Connection, init: &str) {
assert!(
server.handle_message_conn(init, conn).await.is_some(),
"initialize handshake must produce a response",
);
}
async fn call_text(server: &McpServer, conn: &mut Connection, msg: &str) -> String {
let outcome = server
.handle_message_conn(msg, conn)
.await
.expect("a response is expected");
let responses = outcome.into_responses();
responses[0].result.as_ref().expect("result present")["content"][0]["text"]
.as_str()
.expect("text content")
.to_owned()
}
#[tokio::test]
async fn distinct_connections_adopt_their_own_caller() {
let server = identity_server(true);
let mut conn_a = Connection::new();
init_conn(&server, &mut conn_a, INIT_ALICE).await;
assert_eq!(call_text(&server, &mut conn_a, WHOAMI).await, "alice");
let mut conn_b = Connection::new();
init_conn(&server, &mut conn_b, INIT_BOB).await;
assert_eq!(call_text(&server, &mut conn_b, WHOAMI).await, "bob");
assert_eq!(call_text(&server, &mut conn_a, WHOAMI).await, "alice");
}
#[tokio::test]
async fn per_connection_identity_shares_extensions() {
let server = identity_server(true);
let mut conn = Connection::new();
init_conn(&server, &mut conn, INIT_ALICE).await;
assert_eq!(
call_text(&server, &mut conn, ECHO_EXT).await,
"alice:sess42"
);
}
#[tokio::test]
async fn missing_client_info_falls_back_to_shared_identity() {
let server = identity_server(true);
let mut conn = Connection::new();
init_conn(&server, &mut conn, INIT_NO_NAME).await;
assert_eq!(call_text(&server, &mut conn, WHOAMI).await, "server");
}
#[tokio::test]
async fn disabled_flag_ignores_client_info() {
let server = identity_server(false);
let mut conn = Connection::new();
init_conn(&server, &mut conn, INIT_ALICE).await;
assert_eq!(call_text(&server, &mut conn, WHOAMI).await, "server");
}
async fn tcp_whoami(addr: std::net::SocketAddr, init: &str) -> String {
let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
stream
.write_all(format!("{init}\n{WHOAMI}\n").as_bytes())
.await
.unwrap();
stream.flush().await.unwrap();
let mut reader = tokio::io::BufReader::new(stream);
let mut init_line = String::new();
reader.read_line(&mut init_line).await.unwrap();
let mut who_line = String::new();
reader.read_line(&mut who_line).await.unwrap();
let resp: serde_json::Value = serde_json::from_str(who_line.trim()).unwrap();
resp["result"]["content"][0]["text"]
.as_str()
.unwrap()
.to_owned()
}
#[tokio::test]
async fn tcp_connections_keep_independent_identities() {
let server = identity_server(true);
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
tokio::spawn(async move {
let _ = server.run_tcp_listener(listener).await;
});
let (alice, bob) = tokio::join!(tcp_whoami(addr, INIT_ALICE), tcp_whoami(addr, INIT_BOB));
assert_eq!(alice, "alice");
assert_eq!(bob, "bob");
}