From 62e4d30b7cf6d384bdab83515afa864d8d1b92c3 Mon Sep 17 00:00:00 2001 From: Xiwei Pan Date: Fri, 18 Sep 2026 00:01:21 +0800 Subject: [PATCH 1/4] Remove unused dependencies and narrow Tokio features --- Cargo.toml | 1 - problemreductions-cli/Cargo.toml | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1fa1906fa..64b6cd767 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,6 @@ num-rational = "0.4" num-traits = "0.2" good_lp = { version = "=1.14.2", default-features = false, features = ["highs"] } inventory = "0.3" -ordered-float = "5.0" rand = "0.10" criterion = { version = "0.8", optional = true } problemreductions-macros = { version = "0.6.0", path = "problemreductions-macros" } diff --git a/problemreductions-cli/Cargo.toml b/problemreductions-cli/Cargo.toml index be37a9085..25b80054b 100644 --- a/problemreductions-cli/Cargo.toml +++ b/problemreductions-cli/Cargo.toml @@ -17,7 +17,7 @@ path = "src/bin/pred_sym.rs" [features] all = ["mcp"] -mcp = ["dep:rmcp", "dep:tokio", "dep:schemars", "dep:tracing", "dep:tracing-subscriber"] +mcp = ["dep:rmcp", "dep:tokio", "dep:schemars", "dep:tracing-subscriber"] [dependencies] problemreductions = { version = "0.6.0", path = "..", features = ["example-db"] } @@ -29,7 +29,6 @@ num-bigint = "0.4" clap_complete = "4" owo-colors = { version = "4", features = ["supports-colors"] } rmcp = { version = "1.2", features = ["server", "macros", "transport-io"], optional = true } -tokio = { version = "1", features = ["full"], optional = true } +tokio = { version = "1", features = ["rt-multi-thread"], optional = true } schemars = { version = "1.0", optional = true } -tracing = { version = "0.1", optional = true } tracing-subscriber = { version = "0.3", optional = true } From 0060a5ac859bd70be0acdbf6462e198f5830ff4c Mon Sep 17 00:00:00 2001 From: Xiwei Pan Date: Fri, 18 Sep 2026 01:09:58 +0800 Subject: [PATCH 2/4] Update dependency versions and adapt MCP and macro internals --- Cargo.toml | 24 ++++---- problemreductions-cli/Cargo.toml | 22 ++++---- problemreductions-cli/src/mcp/prompts.rs | 9 +-- problemreductions-cli/src/mcp/tools.rs | 14 +++-- .../tests/mcp_integration.rs | 56 ++++++++++++++++++- problemreductions-expr/Cargo.toml | 12 ++-- problemreductions-macros/Cargo.toml | 8 +-- problemreductions-macros/src/lib.rs | 2 +- 8 files changed, 99 insertions(+), 48 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1fa1906fa..39eb9b3bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,25 +21,25 @@ example-db = [] benchmarks = ["dep:criterion"] [dependencies] -petgraph = { version = "0.8", features = ["serde-1"] } -bitvec = "1.0" -serde = { version = "1.0", features = ["derive"] } +petgraph = { version = "0.8.3", features = ["serde-1"] } +bitvec = "1.1.1" +serde = { version = "1.0.229", features = ["derive"] } # Persisted problem and reduction data must preserve every finite f64 on replay. -serde_json = { version = "1.0", features = ["float_roundtrip"] } -thiserror = "2.0" -num-bigint = "0.4" -num-rational = "0.4" -num-traits = "0.2" +serde_json = { version = "1.0.151", features = ["float_roundtrip"] } +thiserror = "2.0.20" +num-bigint = "0.4.8" +num-rational = "0.4.2" +num-traits = "0.2.19" good_lp = { version = "=1.14.2", default-features = false, features = ["highs"] } -inventory = "0.3" +inventory = "0.3.24" ordered-float = "5.0" -rand = "0.10" -criterion = { version = "0.8", optional = true } +rand = "0.10.2" +criterion = { version = "0.8.2", optional = true } problemreductions-macros = { version = "0.6.0", path = "problemreductions-macros" } problemreductions-expr = { version = "0.6.0", path = "problemreductions-expr" } [dev-dependencies] -proptest = "1.0" +proptest = "1.11.0" [[bench]] name = "solver_benchmarks" diff --git a/problemreductions-cli/Cargo.toml b/problemreductions-cli/Cargo.toml index be37a9085..8936d37a9 100644 --- a/problemreductions-cli/Cargo.toml +++ b/problemreductions-cli/Cargo.toml @@ -21,15 +21,15 @@ mcp = ["dep:rmcp", "dep:tokio", "dep:schemars", "dep:tracing", "dep:tracing-subs [dependencies] problemreductions = { version = "0.6.0", path = "..", features = ["example-db"] } -clap = { version = "4", features = ["derive", "string"] } -anyhow = "1" -serde = { version = "1", features = ["derive"] } -serde_json = "1" -num-bigint = "0.4" -clap_complete = "4" -owo-colors = { version = "4", features = ["supports-colors"] } -rmcp = { version = "1.2", features = ["server", "macros", "transport-io"], optional = true } -tokio = { version = "1", features = ["full"], optional = true } -schemars = { version = "1.0", optional = true } +clap = { version = "4.6.7", features = ["derive", "string"] } +anyhow = "1.0.104" +serde = { version = "1.0.229", features = ["derive"] } +serde_json = "1.0.151" +num-bigint = "0.4.8" +clap_complete = "4.6.11" +owo-colors = { version = "4.4.0", features = ["supports-colors"] } +rmcp = { version = "3.4.0", features = ["server", "macros", "transport-io"], optional = true } +tokio = { version = "1.53.1", features = ["full"], optional = true } +schemars = { version = "1.2.2", optional = true } tracing = { version = "0.1", optional = true } -tracing-subscriber = { version = "0.3", optional = true } +tracing-subscriber = { version = "0.3.23", optional = true } diff --git a/problemreductions-cli/src/mcp/prompts.rs b/problemreductions-cli/src/mcp/prompts.rs index 61ac602df..82fbd18b1 100644 --- a/problemreductions-cli/src/mcp/prompts.rs +++ b/problemreductions-cli/src/mcp/prompts.rs @@ -1,4 +1,4 @@ -use rmcp::model::{GetPromptResult, Prompt, PromptArgument, PromptMessage, PromptMessageRole}; +use rmcp::model::{GetPromptResult, Prompt, PromptArgument, PromptMessage, Role}; /// Return the list of available MCP prompt templates. pub fn list_prompts() -> Vec { @@ -88,11 +88,8 @@ pub fn list_prompts() -> Vec { } fn prompt_result(description: &str, user_message: &str) -> GetPromptResult { - GetPromptResult::new(vec![PromptMessage::new_text( - PromptMessageRole::User, - user_message, - )]) - .with_description(description) + GetPromptResult::new(vec![PromptMessage::new_text(Role::User, user_message)]) + .with_description(description) } /// Return the content for the named prompt, or `None` if the name is unknown. diff --git a/problemreductions-cli/src/mcp/tools.rs b/problemreductions-cli/src/mcp/tools.rs index 09cddd892..0c3da5a08 100644 --- a/problemreductions-cli/src/mcp/tools.rs +++ b/problemreductions-cli/src/mcp/tools.rs @@ -615,14 +615,14 @@ impl McpServer { #[rmcp::tool_handler] impl rmcp::ServerHandler for McpServer { - fn get_info(&self) -> rmcp::model::ServerInfo { + fn get_info(&self) -> rmcp::model::ServerConfig { let capabilities = rmcp::model::ServerCapabilities::builder() .enable_tools() .enable_prompts() .build(); let server_info = rmcp::model::Implementation::new("problemreductions", env!("CARGO_PKG_VERSION")); - rmcp::model::ServerInfo::new(capabilities) + rmcp::model::ServerConfig::new(capabilities) .with_server_info(server_info) .with_instructions( "MCP server for NP-hard problem reductions. \ @@ -647,11 +647,13 @@ impl rmcp::ServerHandler for McpServer { &self, request: rmcp::model::GetPromptRequestParams, _context: rmcp::service::RequestContext, - ) -> Result { + ) -> Result { let args = request.arguments.unwrap_or_default(); - super::prompts::get_prompt(&request.name, &args).ok_or_else(|| { - rmcp::ErrorData::invalid_params(format!("Unknown prompt: {}", request.name), None) - }) + super::prompts::get_prompt(&request.name, &args) + .map(Into::into) + .ok_or_else(|| { + rmcp::ErrorData::invalid_params(format!("Unknown prompt: {}", request.name), None) + }) } } diff --git a/problemreductions-cli/tests/mcp_integration.rs b/problemreductions-cli/tests/mcp_integration.rs index 5542dd1f9..b233f1391 100644 --- a/problemreductions-cli/tests/mcp_integration.rs +++ b/problemreductions-cli/tests/mcp_integration.rs @@ -115,7 +115,7 @@ mod mcp_tests { } #[test] - fn test_mcp_server_initialize_and_list_tools() { + fn test_mcp_server_initialize_list_and_call_tools() { let (mut stdin, mut reader, child) = spawn_mcp(); initialize(&mut stdin, &mut reader); @@ -192,11 +192,30 @@ mod mcp_tests { ); } + send( + &mut stdin, + &serde_json::json!({ + "jsonrpc": "2.0", + "id": 3, + "method": "tools/call", + "params": {"name": "list_problems", "arguments": {}} + }), + ); + let call_resp = read_response(&mut reader); + assert_eq!(call_resp["id"], 3); + assert!(call_resp.get("error").is_none(), "{call_resp}"); + assert_ne!(call_resp["result"]["isError"], true); + assert!(call_resp["result"]["content"][0]["text"] + .as_str() + .unwrap() + .contains("MaximumIndependentSet")); + assert!(call_resp["result"].get("resultType").is_none()); + shutdown(stdin, child); } #[test] - fn test_mcp_server_prompts_list() { + fn test_mcp_server_prompts_list_and_get() { let (mut stdin, mut reader, child) = spawn_mcp(); initialize(&mut stdin, &mut reader); @@ -244,6 +263,39 @@ mod mcp_tests { assert!(prompt_names.contains(&"find_reduction")); assert!(prompt_names.contains(&"overview")); + send( + &mut stdin, + &serde_json::json!({ + "jsonrpc": "2.0", + "id": 3, + "method": "prompts/get", + "params": {"name": "what_is", "arguments": {"problem": "QUBO"}} + }), + ); + let prompt_resp = read_response(&mut reader); + assert_eq!(prompt_resp["id"], 3); + let message = &prompt_resp["result"]["messages"][0]; + assert_eq!(message["role"], "user"); + assert_eq!(message["content"]["type"], "text"); + assert!(message["content"]["text"] + .as_str() + .unwrap() + .contains("QUBO")); + + send( + &mut stdin, + &serde_json::json!({ + "jsonrpc": "2.0", + "id": 4, + "method": "prompts/get", + "params": {"name": "unknown"} + }), + ); + let error_resp = read_response(&mut reader); + assert_eq!(error_resp["id"], 4); + assert_eq!(error_resp["error"]["code"], -32602); + assert_eq!(error_resp["error"]["message"], "Unknown prompt: unknown"); + shutdown(stdin, child); } } diff --git a/problemreductions-expr/Cargo.toml b/problemreductions-expr/Cargo.toml index d19a1b770..09304c30f 100644 --- a/problemreductions-expr/Cargo.toml +++ b/problemreductions-expr/Cargo.toml @@ -7,11 +7,11 @@ license = "MIT" repository = "https://github.com/CodingThrust/problem-reductions" [dependencies] -num-bigint = { version = "0.4", features = ["serde"] } -num-rational = { version = "0.4", features = ["serde"] } -num-traits = "0.2" -serde = { version = "1.0", features = ["derive"] } -thiserror = "2.0" +num-bigint = { version = "0.4.8", features = ["serde"] } +num-rational = { version = "0.4.2", features = ["serde"] } +num-traits = "0.2.19" +serde = { version = "1.0.229", features = ["derive"] } +thiserror = "2.0.20" [dev-dependencies] -serde_json = "1.0" +serde_json = "1.0.151" diff --git a/problemreductions-macros/Cargo.toml b/problemreductions-macros/Cargo.toml index 16b94ead5..d20910c69 100644 --- a/problemreductions-macros/Cargo.toml +++ b/problemreductions-macros/Cargo.toml @@ -10,8 +10,8 @@ repository = "https://github.com/CodingThrust/problem-reductions" proc-macro = true [dependencies] -syn = { version = "2.0", features = ["full", "parsing"] } -quote = "1.0" -proc-macro2 = "1.0" +syn = { version = "3.0.6", features = ["full", "parsing"] } +quote = "1.0.47" +proc-macro2 = "1.0.107" problemreductions-expr = { version = "0.6.0", path = "../problemreductions-expr" } -num-traits = "0.2" +num-traits = "0.2.19" diff --git a/problemreductions-macros/src/lib.rs b/problemreductions-macros/src/lib.rs index 9ee54082e..4bb5e88c0 100644 --- a/problemreductions-macros/src/lib.rs +++ b/problemreductions-macros/src/lib.rs @@ -503,7 +503,7 @@ fn generate_reduction_entry( let trait_path = impl_block .trait_ .as_ref() - .map(|(_, path, _)| path) + .map(|(path, _)| path) .ok_or_else(|| syn::Error::new_spanned(impl_block, "Expected impl ReduceTo for S"))?; // Extract target type from ReduceTo From 838a9752bb521b32dd785ee28d5d842bb40998d7 Mon Sep 17 00:00:00 2001 From: Xiwei Pan Date: Fri, 18 Sep 2026 02:19:56 +0800 Subject: [PATCH 3/4] Lint every workspace crate and feature in CI and make clippy --- .github/workflows/ci.yml | 4 ++-- Makefile | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3ef1a9fd8..69cf40e82 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,7 +38,7 @@ jobs: - name: Check formatting run: cargo fmt --all --check - # Lints. + # Lints every workspace crate with every feature (CLI `mcp`, `benchmarks`). clippy: name: Clippy runs-on: ubuntu-latest @@ -49,7 +49,7 @@ jobs: components: clippy - uses: Swatinem/rust-cache@v2 - name: Run clippy - run: cargo clippy --all-targets --features example-db -- -D warnings + run: cargo clippy --workspace --all-targets --all-features -- -D warnings # Build and exercise the HiGHS-backed CLI natively on Apple Silicon. macos-arm64: diff --git a/Makefile b/Makefile index 1cb637f32..14ed14d47 100644 --- a/Makefile +++ b/Makefile @@ -89,7 +89,7 @@ fmt-check: # Run clippy clippy: - cargo clippy --all-targets --features "$(TEST_FEATURES)" -- -D warnings + cargo clippy --workspace --all-targets --all-features -- -D warnings node_modules/elkjs/package.json: package.json package-lock.json npm ci From 95c9315deb0014440c258efda016860e20a1334c Mon Sep 17 00:00:00 2001 From: Xiwei Pan Date: Fri, 18 Sep 2026 03:27:18 +0800 Subject: [PATCH 4/4] Clarify test limits and enforce the coverage target --- .config/nextest.toml | 12 ++---------- codecov.yml | 4 ++-- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/.config/nextest.toml b/.config/nextest.toml index 402f0ff76..a4e5c4b83 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -7,15 +7,7 @@ # whole job was SIGTERM-killed at the runner level) — nextest reports it as a # clean per-test timeout failure instead of stalling the job. # -# The large headroom (300s, not a tight ~10s) is deliberate. The subprocess -# example tests in tests/suites/examples.rs shell out to `cargo run --example -# … --features ilp-highs`: -# - In the Test job, CI pre-builds those examples (see ci.yml) so the -# subprocess reuses artifacts and each test runs in well under a second. -# - In the Code Coverage job, the subprocess inherits llvm-cov's -# `-C instrument-coverage` RUSTFLAGS, so it recompiles the examples -# *instrumented* (a non-instrumented pre-build would not match its -# fingerprint, so pre-building there is pointless). That instrumented -# recompile legitimately takes >120s, hence the 300s bound. +# The large headroom (300s, not a tight ~10s) is deliberate: this is a final +# safety bound for genuinely hung tests, not the expected runtime budget. [profile.default] slow-timeout = { period = "60s", terminate-after = 5 } diff --git a/codecov.yml b/codecov.yml index 27c6f0cd2..7091d9e6f 100644 --- a/codecov.yml +++ b/codecov.yml @@ -10,11 +10,11 @@ coverage: project: default: target: 95% - threshold: 2% + threshold: 0% patch: default: target: 95% - threshold: 2% + threshold: 0% # Exclude proc-macro crate from coverage since it runs at compile time # and traditional runtime coverage tools cannot measure it.