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
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ jobs:
# Exercise the released grep command against real files and exported
# synonym graphs, including Linux-only hostile filesystem names.
- run: cargo test --locked -p terraphim_grep --test synonym_retrieval --test answer_request --test path_guard_hostile_tree
- name: Verify authored synonyms and role boundaries over real MCP stdio
run: cargo test --locked -p terraphim_mcp_server --test test_mcp_role_contract
# GitHub-side home for the updater integration contracts (managed-mode
# receipt/refusal, strict v2 manifest readers, R2 checksum fail-closed).
# On Gitea these lanes live in .gitea/workflows/native-ci.yml, which is
Expand Down
584 changes: 391 additions & 193 deletions crates/terraphim_mcp_server/src/lib.rs

Large diffs are not rendered by default.

11 changes: 6 additions & 5 deletions crates/terraphim_mcp_server/tests/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ async fn test_search_invalid_pagination_params() -> Result<()> {
let transport = TokioChildProcess::new(command)?;
let service = ().serve(transport).await?;

// Negative limit – server may coerce or error; ensure it does not crash.
// Negative pagination is a protocol error, before any retrieval occurs.
let res = service
.call_tool(CallToolRequestParam {
name: "search".into(),
Expand All @@ -472,10 +472,10 @@ async fn test_search_invalid_pagination_params() -> Result<()> {
.as_object()
.cloned(),
})
.await?;
assert!(res.is_error.unwrap_or(false) || !res.content.is_empty());
.await;
assert!(res.is_err(), "negative limit must be rejected: {res:?}");

// Excessive limit should error
// A large in-range limit remains valid and does not terminate the server.
let res2 = service
.call_tool(CallToolRequestParam {
name: "search".into(),
Expand All @@ -487,7 +487,8 @@ async fn test_search_invalid_pagination_params() -> Result<()> {
.cloned(),
})
.await?;
assert!(res2.is_error.unwrap_or(false) || !res2.content.is_empty());
assert!(!res2.is_error.unwrap_or(false));
assert!(!res2.content.is_empty());
Ok(())
}

Expand Down
162 changes: 0 additions & 162 deletions crates/terraphim_mcp_server/tests/mcp_autocomplete_e2e_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,6 @@ async fn create_autocomplete_test_config() -> Result<String> {
async fn start_mcp_server() -> Result<TokioChildProcess> {
let mut cmd = Command::new(env!("CARGO_BIN_EXE_terraphim_mcp_server"));

if std::env::var_os("CI").is_some() {
cmd.arg("--features").arg("zlob");
cmd.arg("--release");
}

cmd.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped());
Expand Down Expand Up @@ -190,106 +185,6 @@ async fn test_build_autocomplete_index_terraphim_engineer() -> Result<()> {
Ok(())
}

/// Test fuzzy autocomplete search using Jaro-Winkler algorithm with knowledge graph terms
#[tokio::test]
#[serial]
#[ignore = "Test expects specific autocomplete results that may not be available"]
async fn test_fuzzy_autocomplete_search_kg_terms() -> Result<()> {
println!("🧪 Testing fuzzy autocomplete search with knowledge graph terms");

let config_json = create_autocomplete_test_config().await?;
let transport = start_mcp_server().await?;
let service = ().serve(transport).await?;

// Update configuration
let update_config_args = json!({"config_str": config_json});
let _update_result = service
.call_tool(CallToolRequestParam {
name: "update_config_tool".into(),
arguments: Some(update_config_args.as_object().unwrap().clone()),
})
.await?;

// Build autocomplete index
let build_index_args = json!({"role": "Terraphim Engineer"});
let build_result = service
.call_tool(CallToolRequestParam {
name: "build_autocomplete_index".into(),
arguments: Some(build_index_args.as_object().unwrap().clone()),
})
.await?;

assert!(build_result.is_error != Some(true));

// Test various knowledge graph terms
let test_queries = vec![
("terrapi", vec!["terraphim-graph"]), // Partial match
("graph", vec!["terraphim-graph", "graph embeddings"]), // Common term
("embedd", vec!["graph embeddings"]), // Partial embedding
("hayst", vec!["haystack"]), // Haystack term
("servic", vec!["service"]), // Service term
("machine", vec![]), // Should not match KG terms
];

for (query, expected_terms) in test_queries {
println!("🔍 Testing query: '{}'", query);

let search_args = json!({
"query": query,
"similarity": 0.6,
"limit": 10
});

let search_result = service
.call_tool(CallToolRequestParam {
name: "fuzzy_autocomplete_search".into(),
arguments: Some(search_args.as_object().unwrap().clone()),
})
.await?;

println!("Search result for '{}': {:?}", query, search_result);
assert!(search_result.is_error != Some(true));

// Check if expected terms are found
if let Some(summary_content) = search_result.content.first() {
let summary_text = summary_content.as_text().unwrap().text.clone();
println!("Summary: {}", summary_text);

// For terms that should match, verify suggestions are returned
if !expected_terms.is_empty() {
assert!(summary_text.contains("Found"));
assert!(!summary_text.contains("Found 0"));

// Check individual suggestions
for (i, content) in search_result.content.iter().skip(1).enumerate() {
let suggestion_text = content.as_text().unwrap().text.clone();
println!("Suggestion {}: {}", i + 1, suggestion_text);

// Verify suggestions contain expected terms
let suggestion_contains_expected = expected_terms.iter().any(|expected| {
suggestion_text
.to_lowercase()
.contains(&expected.to_lowercase())
});

if !suggestion_contains_expected {
println!(
"⚠️ Suggestion '{}' doesn't contain expected terms: {:?}",
suggestion_text, expected_terms
);
}
}
} else {
// For queries that shouldn't match, verify few or no results
println!("Query '{}' expected to have no matches", query);
}
}
}

println!("✅ Fuzzy autocomplete search testing completed");
Ok(())
}

/// Test Levenshtein autocomplete search for comparison
#[tokio::test]
#[serial]
Expand Down Expand Up @@ -456,63 +351,6 @@ async fn test_autocomplete_algorithm_comparison() -> Result<()> {
Ok(())
}

/// Test new autocomplete_terms tool (prefix + fuzzy)
#[tokio::test]
#[serial]
#[ignore = "Test expects specific autocomplete results that may not be available"]
async fn test_autocomplete_terms_tool() -> Result<()> {
let config_json = create_autocomplete_test_config().await?;
let transport = start_mcp_server().await?;
let service = ().serve(transport).await?;

// Update configuration
let update_config_args = json!({"config_str": config_json});
service
.call_tool(CallToolRequestParam {
name: "update_config_tool".into(),
arguments: Some(update_config_args.as_object().unwrap().clone()),
})
.await?;

// Build index
let build_index_args = json!({"role": "Terraphim Engineer"});
let build_result = service
.call_tool(CallToolRequestParam {
name: "build_autocomplete_index".into(),
arguments: Some(build_index_args.as_object().unwrap().clone()),
})
.await?;
assert!(build_result.is_error != Some(true));

// Call autocomplete_terms: expect graph expansion to include synonyms of same concept id
let ac_args = json!({"query": "terraph", "limit": 8});
let ac_result = service
.call_tool(CallToolRequestParam {
name: "autocomplete_terms".into(),
arguments: Some(ac_args.as_object().unwrap().clone()),
})
.await?;
assert!(ac_result.is_error != Some(true));
if let Some(summary) = ac_result.content.first() {
let text = summary.as_text().unwrap().text.clone();
assert!(text.contains("Found"));
}
// Verify that related terms like "graph" or "graph embeddings" appear via concept expansion
let all_texts: Vec<String> = ac_result
.content
.iter()
.skip(1)
.filter_map(|c| c.as_text().map(|t| t.text.clone()))
.collect();
let has_graph_related = all_texts.iter().any(|t| t.to_lowercase().contains("graph"));
assert!(
has_graph_related,
"Autocomplete should include graph-related synonyms via concept expansion: {:?}",
all_texts
);
Ok(())
}

/// Test new autocomplete_with_snippets tool
#[tokio::test]
#[serial]
Expand Down
Loading
Loading