Skip to main content

github_mcp/tools/
search_tool.rs

1// GitHub v3 REST API MCP server — generated by mcpify. Do not hand-edit.
2
3use rusqlite::Connection;
4
5use crate::data::store::search_endpoints;
6use crate::services::embedding_service::embed;
7
8/// Cheap diagnostic: compares `semantic_endpoints` row count against
9/// `endpoints` row count for the store behind `conn`. A store can end up
10/// with `endpoints` rows but no matching `semantic_endpoints` rows if the
11/// `populate-embeddings` step silently skipped or partially failed some
12/// rows — this surfaces that as a warning without changing the
13/// success/`[]` return contract for legitimately-empty search results.
14fn warn_if_embeddings_incomplete(conn: &Connection) -> bool {
15    let counts: rusqlite::Result<(i64, i64)> = conn.query_row(
16        "SELECT (SELECT COUNT(*) FROM endpoints), (SELECT COUNT(*) FROM semantic_endpoints)",
17        [],
18        |row| Ok((row.get(0)?, row.get(1)?)),
19    );
20    let incomplete = matches!(&counts, Ok((endpoints_count, semantic_count)) if semantic_count < endpoints_count);
21    if let Ok((endpoints_count, semantic_count)) = counts
22        && incomplete
23    {
24        tracing::warn!(
25            endpoints_count,
26            semantic_count,
27            "semantic_endpoints is missing {} row(s) compared to endpoints — search results \
28             may be incomplete; re-run populate-embeddings for this store",
29            endpoints_count - semantic_count
30        );
31    }
32    incomplete
33}
34
35/// Semantic similarity lookup matching a natural-language query against
36/// candidate API operations (PRD §1.5) — so an LLM never needs the full
37/// OpenAPI spec in context to find the right operation.
38pub fn search_operations(
39    conn: &Connection,
40    query: &str,
41    limit: usize,
42) -> anyhow::Result<serde_json::Value> {
43    warn_if_embeddings_incomplete(conn);
44    let query_embedding = embed(query)?;
45    let results = search_endpoints(conn, &query_embedding, limit)?;
46    Ok(serde_json::to_value(results)?)
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52
53    #[test]
54    fn incomplete_embedding_catalog_diagnostic_handles_every_count_shape() {
55        let conn = Connection::open_in_memory().unwrap();
56        conn.execute_batch(
57            "CREATE TABLE endpoints (operation_id TEXT); \
58             CREATE TABLE semantic_endpoints (operation_id TEXT); \
59             INSERT INTO endpoints VALUES ('missing');",
60        )
61        .unwrap();
62        assert!(warn_if_embeddings_incomplete(&conn));
63        conn.execute("INSERT INTO semantic_endpoints VALUES ('missing')", [])
64            .unwrap();
65        assert!(!warn_if_embeddings_incomplete(&conn));
66    }
67}