codelens-mcp 1.9.38

Harness-native Rust MCP server for code intelligence — 107 tools, 25 languages, tree-sitter + hybrid semantic search, 6.1x fewer tokens than rg+cat on agent tasks
use super::{
    AppState, ToolResult, optional_bool, optional_string, optional_usize, required_string,
    success_meta,
};
use crate::client_profile::ClientProfile;
use crate::error::CodeLensError;
use crate::protocol::BackendKind;
use codelens_engine::{
    detect_frameworks, detect_workspace_packages, find_files, list_dir, read_file,
    search_for_pattern, search_for_pattern_smart,
};
use serde_json::{Value, json};

/// Load `.codelens/config.json` as project-level config policy.
/// Returns null if file doesn't exist or is malformed.
///
/// Format: `{"semantic_search": true, "mutation_gates": true, "bridge_loading": true, ...}`
fn load_project_config_policy(project_root: &std::path::Path) -> Value {
    let path = project_root.join(".codelens/config.json");
    match std::fs::read_to_string(&path) {
        Ok(content) => serde_json::from_str(&content).unwrap_or(Value::Null),
        Err(_) => Value::Null,
    }
}

pub fn get_current_config(state: &AppState, arguments: &serde_json::Value) -> ToolResult {
    let stats = state.symbol_index().stats()?;
    let session = crate::session_context::SessionRequestContext::from_json(arguments);
    let surface = state.execution_surface(&session);
    let token_budget = state.execution_token_budget(&session);
    let client_profile = session
        .client_name
        .as_deref()
        .map(|name| ClientProfile::detect(Some(name)))
        .unwrap_or_else(|| state.client_profile());
    let frameworks = detect_frameworks(state.project().as_path());
    let workspace_packages = detect_workspace_packages(state.project().as_path());

    // Load project-level config policy from .codelens/config.json (if present).
    // This allows enterprise deployments to control feature flags per-project.
    let config_policy = load_project_config_policy(state.project().as_path());

    Ok((
        json!({
            "runtime": "rust-core",
            "project_root": state.project().as_path().display().to_string(),
            "editor_integration": false,
            "available_backends": ["filesystem", "tree-sitter-cached", "lsp_pooled"],
            "symbol_index": stats,
            "surface": surface.as_label(),
            "token_budget": token_budget,
            "tool_count": crate::tool_defs::visible_tools(surface).len(),
            "client_profile": client_profile.as_str(),
            "frameworks": frameworks,
            "workspace_packages": workspace_packages,
            "config_policy": config_policy,
        }),
        success_meta(BackendKind::Config, 1.0),
    ))
}

pub fn read_file_tool(state: &AppState, arguments: &serde_json::Value) -> ToolResult {
    let path = required_string(arguments, "relative_path")?;
    let start_line = arguments
        .get("start_line")
        .and_then(|v| v.as_u64())
        .map(|v| v as usize);
    let end_line = arguments
        .get("end_line")
        .and_then(|v| v.as_u64())
        .map(|v| v as usize);
    Ok(read_file(&state.project(), path, start_line, end_line)
        .map(|value| (json!(value), success_meta(BackendKind::Filesystem, 1.0)))?)
}

pub fn list_dir_tool(state: &AppState, arguments: &serde_json::Value) -> ToolResult {
    let path = required_string(arguments, "relative_path")?;
    let recursive = optional_bool(arguments, "recursive", false);
    Ok(list_dir(&state.project(), path, recursive).map(|value| {
        (
            json!({ "entries": value, "count": value.len() }),
            success_meta(BackendKind::Filesystem, 1.0),
        )
    })?)
}

pub fn find_file_tool(state: &AppState, arguments: &serde_json::Value) -> ToolResult {
    let pattern = required_string(arguments, "wildcard_pattern")?;
    let dir = optional_string(arguments, "relative_dir");
    Ok(find_files(&state.project(), pattern, dir).map(|value| {
        (
            json!({ "files": value, "count": value.len() }),
            success_meta(BackendKind::Filesystem, 1.0),
        )
    })?)
}

pub fn search_for_pattern_tool(state: &AppState, arguments: &serde_json::Value) -> ToolResult {
    let pattern = arguments
        .get("pattern")
        .or_else(|| arguments.get("substring_pattern"))
        .and_then(|v| v.as_str())
        .ok_or_else(|| CodeLensError::MissingParam("pattern".into()))?;
    let file_glob = optional_string(arguments, "file_glob");
    let max_results = optional_usize(arguments, "max_results", 50);
    let smart = optional_bool(arguments, "smart", false);
    let ctx_fallback = optional_usize(arguments, "context_lines", 0);
    let ctx_before = optional_usize(arguments, "context_lines_before", ctx_fallback);
    let ctx_after = optional_usize(arguments, "context_lines_after", ctx_fallback);

    if smart {
        Ok(search_for_pattern_smart(
            &state.project(),
            pattern,
            file_glob,
            max_results,
            ctx_before,
            ctx_after,
        )
        .map(|value| {
            (
                json!({ "matches": value, "count": value.len() }),
                success_meta(BackendKind::TreeSitter, 0.96),
            )
        })?)
    } else {
        Ok(search_for_pattern(
            &state.project(),
            pattern,
            file_glob,
            max_results,
            ctx_before,
            ctx_after,
        )
        .map(|value| {
            (
                json!({ "matches": value, "count": value.len() }),
                success_meta(BackendKind::Filesystem, 0.98),
            )
        })?)
    }
}

pub fn find_annotations(state: &AppState, arguments: &serde_json::Value) -> ToolResult {
    let tags = optional_string(arguments, "tags").unwrap_or("TODO,FIXME,HACK,DEPRECATED,XXX,NOTE");
    let max_results = optional_usize(arguments, "max_results", 100);
    let tag_list = tags
        .split(',')
        .map(str::trim)
        .filter(|tag| !tag.is_empty())
        .collect::<Vec<_>>();
    let pattern = format!(r"\b({})\b[:\s]*(.*)", tag_list.join("|"));
    Ok(
        search_for_pattern(&state.project(), &pattern, None, max_results, 0, 0).map(|value| {
            let grouped = tag_list
                .iter()
                .filter_map(|tag| {
                    let matches = value
                        .iter()
                        .filter(|entry| {
                            entry.matched_text.eq_ignore_ascii_case(tag)
                                || entry.line_content.contains(tag)
                        })
                        .map(|entry| {
                            json!({
                                "file": entry.file_path,
                                "line": entry.line,
                                "text": entry.line_content
                            })
                        })
                        .collect::<Vec<_>>();
                    if matches.is_empty() {
                        None
                    } else {
                        Some(((*tag).to_owned(), serde_json::Value::Array(matches)))
                    }
                })
                .collect::<serde_json::Map<String, serde_json::Value>>();
            (
                json!({ "tags": grouped, "total": value.len() }),
                success_meta(BackendKind::Filesystem, 0.97),
            )
        })?,
    )
}

pub fn find_tests(state: &AppState, arguments: &serde_json::Value) -> ToolResult {
    let max_results = optional_usize(arguments, "max_results", 100);
    let pattern = r"\b(def test_|func Test|@Test\b|it\s*\(|describe\s*\(|test\s*\()";
    Ok(
        search_for_pattern(&state.project(), pattern, None, max_results, 0, 0).map(|value| {
            (
                json!({ "tests": value, "count": value.len() }),
                success_meta(BackendKind::Filesystem, 0.97),
            )
        })?,
    )
}