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
+56 -16

No files matched your search

+6 -6
View File
@@ -5,10 +5,10 @@ default:
@just --list
# Build and deploy everything across Windows and WSL
all port: build-all (deploy-all port)
all port: (build-all port) (deploy-all port)
# Build all binaries across Windows and WSL
build-all: build-win build-wsl
build-all port: (build-win port) build-wsl
@Write-Host "Build complete across both OS boundaries." -ForegroundColor Green
# Deploy all binaries and agent rules across Windows and WSL
@@ -16,7 +16,7 @@ deploy-all port: (deploy-win port) deploy-wsl
@Write-Host "Deployment complete across both OS boundaries." -ForegroundColor Green
# Build Windows-native binaries
build-win: build-ui build-server build-stub-win build-nvim-win
build-win port: build-ui (build-server port) build-stub-win build-nvim-win
# Deploy Windows-native binaries and rules
deploy-win port: (deploy-server port) deploy-stub-win deploy-nvim-win deploy-rules-win
@@ -68,7 +68,7 @@ check-ui:
deno check server/src/dashboard.ts
# Build Windows-native server binary in release mode (ensuring fresh UI build)
build-server: build-ui
build-server port: build-ui
@Write-Host "Building Windows Server..." -ForegroundColor Cyan
cargo build --release -p mcp-memory-server
@@ -179,7 +179,7 @@ improve-tests file="server/src/ollama.rs":
# =========================================================
# Complete lifecycle: stop, build, deploy, start, and verify Windows server
all-server-win port: (stop port) build-server (deploy-server port) (start port) (verify port)
all-server-win port: (stop port) (build-server port) (deploy-server port) (start port) (verify port)
# Complete lifecycle: build and deploy Windows stub
all-stub-win: build-stub-win deploy-stub-win
@@ -194,7 +194,7 @@ all-stub-wsl: build-stub deploy-stub
all-nvim-wsl: build-nvim deploy-nvim
# Complete lifecycle: stop, build, deploy, start, and verify WSL server
all-server-wsl port: (stop port) build-server (deploy-server port) (start port) (verify port)
all-server-wsl port: (stop port) (build-server port) (deploy-server port) (start port) (verify port)
# Install git hooks (post-commit reconciliation and pre-push gatekeeper)
install-git-hooks:
+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())
+22 -9
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 {
@@ -778,7 +780,8 @@ 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]
+2
View File
@@ -59,6 +59,8 @@ async fn test_full_system_e2e_performance() {
// 1. Start Server
let _server = ChildGuard(
Command::new(&server_exe)
.arg("--port")
.arg(test_port)
.env("MCP_PORT", test_port)
.env("RUST_LOG", "debug")
.env("MCP_MEMORY_STORE_DIR", temp_dir.to_str().unwrap())