refactor(server): strictly enforce CLI port argument for bind, dropping MCP_PORT env var fallback

This commit is contained in:
Riz Ashraf committed 2026-10-12 06:24:12 +01:00
1 parent 81853dfed7
commit 3b3bb36b32
5 files changed
+57 -17

No files matched your search

+3 -1
View File
@@ -36,7 +36,9 @@ async fn main() {
std::fs::create_dir_all(&temp_dir).unwrap();
let mut child = Command::new(exe_path)
.env("MCP_PORT", port.to_string())
.arg("--port")
.arg(port.to_string())
.env("MCP_PORT", port.to_string()) // Keep for backward compatibility of clients
.env("MCP_UDP_PORT1", udp1.to_string())
.env("MCP_UDP_PORT2", udp2.to_string())
.env("MCP_MEMORY_STORE_DIR", temp_dir.to_str().unwrap())
+23 -10
View File
@@ -45,6 +45,9 @@ pub struct Cli {
/// Send a shutdown request to the existing server and wait for it to exit
#[arg(long)]
pub restart: bool,
/// Port to bind the server to (or to connect to for exit/gate commands)
#[arg(short, long)]
pub port: u16,
}
#[derive(Subcommand)]
@@ -343,7 +346,7 @@ pub async fn memory_consolidation_worker(state: Arc<MemoryState>) {
}
}
pub async fn run_server(state: Arc<MemoryState>) -> Result<(), Box<dyn std::error::Error>> {
pub async fn run_server(state: Arc<MemoryState>, port: u16) -> Result<(), Box<dyn std::error::Error>> {
let state_for_index = Arc::clone(&state);
tokio::spawn(async move {
state_for_index.rebuild_index().await;
@@ -586,12 +589,11 @@ pub async fn run_server(state: Arc<MemoryState>) -> Result<(), Box<dyn std::erro
let app = api::setup::create_router(app_state);
let port_str = crate::config::get_env("MCP_PORT").unwrap_or_else(|_| "3000".to_string());
tracing::info!(
"MCP Memory Server running on http://127.0.0.1:{}/ws",
port_str
port
);
let addr: std::net::SocketAddr = format!("127.0.0.1:{}", port_str)
let addr: std::net::SocketAddr = format!("127.0.0.1:{}", port)
.parse()
.expect("Invalid bind address");
@@ -664,7 +666,7 @@ pub fn run_cli() -> Result<(), Box<dyn std::error::Error>> {
let base = PathBuf::from(base_dir);
if cli.exit {
let port = crate::config::get_env("MCP_PORT").unwrap_or_else(|_| "3000".to_string());
let port = cli.port;
let token = std::fs::read_to_string(base.join("admin.token")).unwrap_or_default();
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
@@ -684,7 +686,7 @@ pub fn run_cli() -> Result<(), Box<dyn std::error::Error>> {
}
if let Some(Commands::Gate { subcmd }) = cli.command {
let port = crate::config::get_env("MCP_PORT").unwrap_or_else(|_| "3000".to_string());
let port = cli.port;
let rt = tokio::runtime::Runtime::new()?;
match subcmd {
GateCommands::Set {
@@ -777,8 +779,9 @@ pub fn run_cli() -> Result<(), Box<dyn std::error::Error>> {
rt.block_on(async {
let state = Arc::new(MemoryState::new(&base.to_string_lossy()));
if let Err(e) = run_server(state).await {
let port = cli.port;
if let Err(e) = run_server(state, port).await {
tracing::error!("Server error: {}", e);
}
});
@@ -793,16 +796,19 @@ mod tests {
#[test]
fn test_cli_parsing_default() {
let cli = Cli::try_parse_from(["mcp-memory-server"]).unwrap();
let cli = Cli::try_parse_from(["mcp-memory-server", "--port", "3000"]).unwrap();
assert!(cli.command.is_none());
assert!(!cli.exit);
assert!(!cli.restart);
assert_eq!(cli.port, 3000);
}
#[test]
fn test_cli_parsing_exit_and_target() {
let cli = Cli::try_parse_from([
"mcp-memory-server",
"--port",
"3000",
"--exit",
"--target",
"http://localhost:3000",
@@ -810,12 +816,15 @@ mod tests {
.unwrap();
assert!(cli.exit);
assert_eq!(cli.target.as_deref(), Some("http://localhost:3000"));
assert_eq!(cli.port, 3000);
}
#[test]
fn test_cli_parsing_gate_set() {
let cli = Cli::try_parse_from([
"mcp-memory-server",
"--port",
"3000",
"gate",
"set",
"--action",
@@ -828,6 +837,7 @@ mod tests {
])
.unwrap();
assert_eq!(cli.port, 3000);
if let Some(Commands::Gate {
subcmd:
GateCommands::Set {
@@ -852,6 +862,8 @@ mod tests {
fn test_cli_parsing_gate_verify() {
let cli = Cli::try_parse_from([
"mcp-memory-server",
"--port",
"3000",
"gate",
"verify",
"--action",
@@ -862,6 +874,7 @@ mod tests {
])
.unwrap();
assert_eq!(cli.port, 3000);
if let Some(Commands::Gate {
subcmd:
GateCommands::Verify {
@@ -933,7 +946,7 @@ mod tests {
}
let server_handle = tokio::spawn(async move {
let _ = run_server(state).await;
let _ = run_server(state, port).await;
});
tokio::time::sleep(Duration::from_millis(300)).await;
+23
View File
@@ -1180,6 +1180,23 @@ pub fn format_tool_activity_description(name: &str, args: &serde_json::Value) ->
.unwrap_or_default();
("Open Nodes", names)
}
"search_nodes" => {
let query = args.get("query").and_then(|v| v.as_str()).unwrap_or("");
("Search Nodes", query.to_string())
}
"get_subgraph" | "summarize_subgraph" => {
let name = args.get("entity_name").and_then(|v| v.as_str()).unwrap_or("");
("Subgraph", name.to_string())
}
"query_graph_path" => {
let source = args.get("source").and_then(|v| v.as_str()).unwrap_or("?");
let target = args.get("target").and_then(|v| v.as_str()).unwrap_or("?");
("Graph Path", format!("{} -> {}", source, target))
}
"condense_entity" => {
let name = args.get("entity_name").and_then(|v| v.as_str()).unwrap_or("");
("Condense Entity", name.to_string())
}
_ => return name.to_string(),
};
if detail.is_empty() {
@@ -1221,6 +1238,12 @@ mod tests {
let msg3 = format_tool_activity_description("read_graph", &json!({}));
assert_eq!(msg3, "read_graph");
let msg4 = format_tool_activity_description(
"search_nodes",
&serde_json::json!({ "query": "database connection" }),
);
assert_eq!(msg4, "Search Nodes: database connection");
}
#[tokio::test]