AkurAI Build
Menu

Public repository / c2f43d4988686a5b5142bb6dd4255f9b473792dc

AkurAI-Notes / crates/notes-mcp/tests/mcp_test.rs

Latest change b78e3a5270c5c5e1b0b04f1da8da4899aa5e4e22 - E06: HTTPS MCP server at /mcp — server.rs + tools.rs + cmd_mcp.rs + bearer auth by Claude Code

use akurai_storage::BTree;
use std::sync::{Arc, Mutex};

fn make_registry() -> notes_mcp::tools::ToolRegistry {
    let dir = std::env::temp_dir().join(format!("notes-mcp-test-{}", std::process::id()));
    let _ = std::fs::create_dir_all(&dir);
    let unique = format!(
        "test-{}.db",
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_nanos()
    );
    let db_path = dir.join(unique);
    let db = BTree::open(&db_path).expect("create test db");
    let owner_sub = "testuser@example.com".to_string();
    notes_mcp::tools::ToolRegistry::new(Arc::new(Mutex::new(db)), None, None, owner_sub)
}

fn create_note_via_tool(
    registry: &notes_mcp::tools::ToolRegistry,
    title: &str,
    body: &str,
    owner: &str,
) {
    let now = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap_or_default()
        .as_secs() as i64;

    let next_id = {
        let mut db = registry.db.lock().unwrap();
        let next = db
            .get(b"_meta:next_note_id")
            .ok()
            .flatten()
            .and_then(|b| String::from_utf8_lossy(&b).parse::<i64>().ok())
            .unwrap_or(1);
        db.insert(
            b"_meta:next_note_id",
            format!("{}", next + 1).as_bytes(),
        )
        .unwrap();
        db.commit().unwrap();
        next
    };

    let note = akurai_json::Value::Object(vec![
        ("id".into(), akurai_json::Value::Int(next_id)),
        ("title".into(), akurai_json::Value::Str(title.into())),
        ("body".into(), akurai_json::Value::Str(body.into())),
        ("ownerSub".into(), akurai_json::Value::Str(owner.into())),
        ("createdAt".into(), akurai_json::Value::Int(now)),
        ("updatedAt".into(), akurai_json::Value::Int(now)),
        ("archived".into(), akurai_json::Value::Bool(false)),
    ]);

    let mut key = b"notes:".to_vec();
    key.extend_from_slice(&(next_id as u64).to_be_bytes());

    let mut db = registry.db.lock().unwrap();
    db.insert(&key, note.to_json().as_bytes()).unwrap();
    db.commit().unwrap();
}

#[test]
fn initialize_returns_server_info() {
    let registry = make_registry();
    let req = r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}"#;
    let resp = registry.handle_request(req);
    assert!(resp.contains("\"jsonrpc\":\"2.0\""));
    assert!(resp.contains("\"protocolVersion\":\"2024-11-05\""));
    assert!(resp.contains("\"akurai-notes-mcp\""));
}

#[test]
fn tools_list_returns_all_notes_tools() {
    let registry = make_registry();
    let req = r#"{"jsonrpc":"2.0","id":2,"method":"tools/list"}"#;
    let resp = registry.handle_request(req);
    assert!(resp.contains("\"search_notes\""));
    assert!(resp.contains("\"semantic_search\""));
    assert!(resp.contains("\"get_note\""));
    assert!(resp.contains("\"list_notebooks\""));
    assert!(resp.contains("\"create_note\""));
    assert!(resp.contains("\"update_note\""));
    assert!(resp.contains("\"delete_note\""));
    assert!(resp.contains("\"add_tag\""));
    assert!(resp.contains("\"remove_tag\""));
}

#[test]
fn bad_json_returns_parse_error() {
    let registry = make_registry();
    let resp = registry.handle_request("not json");
    assert!(resp.contains("\"code\":-32700"));
}

#[test]
fn search_notes_returns_owned_only() {
    let registry = make_registry();
    create_note_via_tool(&registry, "Meeting notes", "Discussed Q3 roadmap", "testuser@example.com");
    create_note_via_tool(&registry, "Other note", "Not relevant", "other@example.com");

    let req = r#"{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"search_notes","arguments":{"query":"meeting"}}}"#;
    let resp = registry.handle_request(req);
    assert!(resp.contains("Meeting notes"));
}

#[test]
fn get_note_returns_full_note() {
    let registry = make_registry();
    create_note_via_tool(&registry, "Test Note", "Hello world", "testuser@example.com");

    let req = r#"{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"get_note","arguments":{"id":1}}}"#;
    let resp = registry.handle_request(req);
    assert!(resp.contains("Test Note"));
    assert!(resp.contains("Hello world"));
}

#[test]
fn create_note_inserts_and_returns_id() {
    let registry = make_registry();
    let req = r#"{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"create_note","arguments":{"title":"New Note","body":"Some body"}}}"#;
    let resp = registry.handle_request(req);
    assert!(resp.contains("\"result\""));

    let get_req = r#"{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"get_note","arguments":{"id":1}}}"#;
    let get_resp = registry.handle_request(get_req);
    assert!(get_resp.contains("New Note"));
}

#[test]
fn unknown_tool_returns_error() {
    let registry = make_registry();
    let req = r#"{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"nonexistent","arguments":{}}}"#;
    let resp = registry.handle_request(req);
    assert!(resp.contains("\"code\":-32603"));
}

#[test]
fn unknown_method_returns_error() {
    let registry = make_registry();
    let req = r#"{"jsonrpc":"2.0","id":8,"method":"bogus"}"#;
    let resp = registry.handle_request(req);
    assert!(resp.contains("\"code\":-32601"));
}