refactor(server): strictly enforce CLI port argument for bind, dropping MCP_PORT env var fallback
This commit is contained in:
1 parent
81853dfed7
commit
3b3bb36b32
5 files changed
+56
-16
No files matched your search
@@ -5,10 +5,10 @@ default:
|
|||||||
@just --list
|
@just --list
|
||||||
|
|
||||||
# Build and deploy everything across Windows and WSL
|
# 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 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
|
@Write-Host "Build complete across both OS boundaries." -ForegroundColor Green
|
||||||
|
|
||||||
# Deploy all binaries and agent rules across Windows and WSL
|
# 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
|
@Write-Host "Deployment complete across both OS boundaries." -ForegroundColor Green
|
||||||
|
|
||||||
# Build Windows-native binaries
|
# 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 Windows-native binaries and rules
|
||||||
deploy-win port: (deploy-server port) deploy-stub-win deploy-nvim-win deploy-rules-win
|
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
|
deno check server/src/dashboard.ts
|
||||||
|
|
||||||
# Build Windows-native server binary in release mode (ensuring fresh UI build)
|
# 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
|
@Write-Host "Building Windows Server..." -ForegroundColor Cyan
|
||||||
cargo build --release -p mcp-memory-server
|
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
|
# 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
|
# Complete lifecycle: build and deploy Windows stub
|
||||||
all-stub-win: build-stub-win deploy-stub-win
|
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
|
all-nvim-wsl: build-nvim deploy-nvim
|
||||||
|
|
||||||
# Complete lifecycle: stop, build, deploy, start, and verify WSL server
|
# 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 (post-commit reconciliation and pre-push gatekeeper)
|
||||||
install-git-hooks:
|
install-git-hooks:
|
||||||
|
|||||||
@@ -36,7 +36,9 @@ async fn main() {
|
|||||||
std::fs::create_dir_all(&temp_dir).unwrap();
|
std::fs::create_dir_all(&temp_dir).unwrap();
|
||||||
|
|
||||||
let mut child = Command::new(exe_path)
|
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_PORT1", udp1.to_string())
|
||||||
.env("MCP_UDP_PORT2", udp2.to_string())
|
.env("MCP_UDP_PORT2", udp2.to_string())
|
||||||
.env("MCP_MEMORY_STORE_DIR", temp_dir.to_str().unwrap())
|
.env("MCP_MEMORY_STORE_DIR", temp_dir.to_str().unwrap())
|
||||||
|
|||||||
+22
-9
@@ -45,6 +45,9 @@ pub struct Cli {
|
|||||||
/// Send a shutdown request to the existing server and wait for it to exit
|
/// Send a shutdown request to the existing server and wait for it to exit
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub restart: bool,
|
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)]
|
#[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);
|
let state_for_index = Arc::clone(&state);
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
state_for_index.rebuild_index().await;
|
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 app = api::setup::create_router(app_state);
|
||||||
|
|
||||||
let port_str = crate::config::get_env("MCP_PORT").unwrap_or_else(|_| "3000".to_string());
|
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
"MCP Memory Server running on http://127.0.0.1:{}/ws",
|
"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()
|
.parse()
|
||||||
.expect("Invalid bind address");
|
.expect("Invalid bind address");
|
||||||
|
|
||||||
@@ -664,7 +666,7 @@ pub fn run_cli() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
let base = PathBuf::from(base_dir);
|
let base = PathBuf::from(base_dir);
|
||||||
|
|
||||||
if cli.exit {
|
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 token = std::fs::read_to_string(base.join("admin.token")).unwrap_or_default();
|
||||||
let rt = tokio::runtime::Runtime::new()?;
|
let rt = tokio::runtime::Runtime::new()?;
|
||||||
rt.block_on(async {
|
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 {
|
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()?;
|
let rt = tokio::runtime::Runtime::new()?;
|
||||||
match subcmd {
|
match subcmd {
|
||||||
GateCommands::Set {
|
GateCommands::Set {
|
||||||
@@ -778,7 +780,8 @@ pub fn run_cli() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
rt.block_on(async {
|
rt.block_on(async {
|
||||||
let state = Arc::new(MemoryState::new(&base.to_string_lossy()));
|
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);
|
tracing::error!("Server error: {}", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -793,16 +796,19 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cli_parsing_default() {
|
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.command.is_none());
|
||||||
assert!(!cli.exit);
|
assert!(!cli.exit);
|
||||||
assert!(!cli.restart);
|
assert!(!cli.restart);
|
||||||
|
assert_eq!(cli.port, 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cli_parsing_exit_and_target() {
|
fn test_cli_parsing_exit_and_target() {
|
||||||
let cli = Cli::try_parse_from([
|
let cli = Cli::try_parse_from([
|
||||||
"mcp-memory-server",
|
"mcp-memory-server",
|
||||||
|
"--port",
|
||||||
|
"3000",
|
||||||
"--exit",
|
"--exit",
|
||||||
"--target",
|
"--target",
|
||||||
"http://localhost:3000",
|
"http://localhost:3000",
|
||||||
@@ -810,12 +816,15 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(cli.exit);
|
assert!(cli.exit);
|
||||||
assert_eq!(cli.target.as_deref(), Some("http://localhost:3000"));
|
assert_eq!(cli.target.as_deref(), Some("http://localhost:3000"));
|
||||||
|
assert_eq!(cli.port, 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cli_parsing_gate_set() {
|
fn test_cli_parsing_gate_set() {
|
||||||
let cli = Cli::try_parse_from([
|
let cli = Cli::try_parse_from([
|
||||||
"mcp-memory-server",
|
"mcp-memory-server",
|
||||||
|
"--port",
|
||||||
|
"3000",
|
||||||
"gate",
|
"gate",
|
||||||
"set",
|
"set",
|
||||||
"--action",
|
"--action",
|
||||||
@@ -828,6 +837,7 @@ mod tests {
|
|||||||
])
|
])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(cli.port, 3000);
|
||||||
if let Some(Commands::Gate {
|
if let Some(Commands::Gate {
|
||||||
subcmd:
|
subcmd:
|
||||||
GateCommands::Set {
|
GateCommands::Set {
|
||||||
@@ -852,6 +862,8 @@ mod tests {
|
|||||||
fn test_cli_parsing_gate_verify() {
|
fn test_cli_parsing_gate_verify() {
|
||||||
let cli = Cli::try_parse_from([
|
let cli = Cli::try_parse_from([
|
||||||
"mcp-memory-server",
|
"mcp-memory-server",
|
||||||
|
"--port",
|
||||||
|
"3000",
|
||||||
"gate",
|
"gate",
|
||||||
"verify",
|
"verify",
|
||||||
"--action",
|
"--action",
|
||||||
@@ -862,6 +874,7 @@ mod tests {
|
|||||||
])
|
])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(cli.port, 3000);
|
||||||
if let Some(Commands::Gate {
|
if let Some(Commands::Gate {
|
||||||
subcmd:
|
subcmd:
|
||||||
GateCommands::Verify {
|
GateCommands::Verify {
|
||||||
@@ -933,7 +946,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let server_handle = tokio::spawn(async move {
|
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;
|
tokio::time::sleep(Duration::from_millis(300)).await;
|
||||||
|
|||||||
@@ -1180,6 +1180,23 @@ pub fn format_tool_activity_description(name: &str, args: &serde_json::Value) ->
|
|||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
("Open Nodes", names)
|
("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(),
|
_ => return name.to_string(),
|
||||||
};
|
};
|
||||||
if detail.is_empty() {
|
if detail.is_empty() {
|
||||||
@@ -1221,6 +1238,12 @@ mod tests {
|
|||||||
|
|
||||||
let msg3 = format_tool_activity_description("read_graph", &json!({}));
|
let msg3 = format_tool_activity_description("read_graph", &json!({}));
|
||||||
assert_eq!(msg3, "read_graph");
|
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]
|
#[tokio::test]
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ async fn test_full_system_e2e_performance() {
|
|||||||
// 1. Start Server
|
// 1. Start Server
|
||||||
let _server = ChildGuard(
|
let _server = ChildGuard(
|
||||||
Command::new(&server_exe)
|
Command::new(&server_exe)
|
||||||
|
.arg("--port")
|
||||||
|
.arg(test_port)
|
||||||
.env("MCP_PORT", test_port)
|
.env("MCP_PORT", test_port)
|
||||||
.env("RUST_LOG", "debug")
|
.env("RUST_LOG", "debug")
|
||||||
.env("MCP_MEMORY_STORE_DIR", temp_dir.to_str().unwrap())
|
.env("MCP_MEMORY_STORE_DIR", temp_dir.to_str().unwrap())
|
||||||
|
|||||||
Reference in new issue
Block a user