Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 2 additions & 10 deletions .config/nextest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down
25 changes: 12 additions & 13 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,24 @@ 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"
ordered-float = "5.0"
rand = "0.10"
criterion = { version = "0.8", optional = true }
inventory = "0.3.24"
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"
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
25 changes: 12 additions & 13 deletions problemreductions-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,18 @@ 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"] }
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 }
tracing = { version = "0.1", optional = true }
tracing-subscriber = { version = "0.3", 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 = ["rt-multi-thread"], optional = true }
schemars = { version = "1.2.2", optional = true }
tracing-subscriber = { version = "0.3.23", optional = true }
9 changes: 3 additions & 6 deletions problemreductions-cli/src/mcp/prompts.rs
Original file line number Diff line number Diff line change
@@ -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<Prompt> {
Expand Down Expand Up @@ -88,11 +88,8 @@ pub fn list_prompts() -> Vec<Prompt> {
}

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.
Expand Down
14 changes: 8 additions & 6 deletions problemreductions-cli/src/mcp/tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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. \
Expand All @@ -647,11 +647,13 @@ impl rmcp::ServerHandler for McpServer {
&self,
request: rmcp::model::GetPromptRequestParams,
_context: rmcp::service::RequestContext<rmcp::RoleServer>,
) -> Result<rmcp::model::GetPromptResult, rmcp::ErrorData> {
) -> Result<rmcp::model::GetPromptResponse, rmcp::ErrorData> {
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)
})
}
}

Expand Down
56 changes: 54 additions & 2 deletions problemreductions-cli/tests/mcp_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);
}
}
12 changes: 6 additions & 6 deletions problemreductions-expr/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
8 changes: 4 additions & 4 deletions problemreductions-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
2 changes: 1 addition & 1 deletion problemreductions-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> for S"))?;

// Extract target type from ReduceTo<Target>
Expand Down
Loading