pathfinder-mcp 0.6.1

Pathfinder — The Headless IDE MCP Server for AI Coding Agents
Documentation
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::needless_return)]
use pathfinder_common::config::PathfinderConfig;
use pathfinder_common::sandbox::Sandbox;
use pathfinder_common::types::WorkspaceRoot;
use pathfinder_lsp::types::{DefinitionLocation, LspDiagnostic};
use pathfinder_lsp::{Lawyer, LspError};
use pathfinder_search::MockScout;
use pathfinder_treesitter::mock::MockSurgeon;
use pathfinder_treesitter::surgeon::BodyRange;
use std::path::Path;
use std::sync::Arc;

pub(crate) struct UnsupportedDiagLawyer;

#[async_trait::async_trait]
impl Lawyer for UnsupportedDiagLawyer {
    async fn goto_definition(
        &self,
        _workspace_root: &Path,
        _file_path: &Path,
        _line: u32,
        _column: u32,
    ) -> Result<Option<DefinitionLocation>, LspError> {
        Ok(None)
    }
    async fn call_hierarchy_prepare(
        &self,
        _workspace_root: &std::path::Path,
        _file_path: &std::path::Path,
        _line: u32,
        _column: u32,
    ) -> Result<Vec<pathfinder_lsp::types::CallHierarchyItem>, LspError> {
        Err(LspError::NoLspAvailable)
    }
    async fn call_hierarchy_incoming(
        &self,
        _workspace_root: &std::path::Path,
        _item: &pathfinder_lsp::types::CallHierarchyItem,
    ) -> Result<Vec<pathfinder_lsp::types::CallHierarchyCall>, LspError> {
        Err(LspError::NoLspAvailable)
    }
    async fn call_hierarchy_outgoing(
        &self,
        _workspace_root: &std::path::Path,
        _item: &pathfinder_lsp::types::CallHierarchyItem,
    ) -> Result<Vec<pathfinder_lsp::types::CallHierarchyCall>, LspError> {
        Err(LspError::NoLspAvailable)
    }
    /// IW-3 (DS-1): Test double — `open_document` returns a no-op lease.
    async fn open_document(
        &self,
        _workspace_root: &Path,
        _file_path: &Path,
        _content: &str,
    ) -> Result<Box<dyn pathfinder_lsp::lawyer::DocumentLease>, pathfinder_lsp::LspError> {
        struct NullLease;
        impl pathfinder_lsp::lawyer::DocumentLease for NullLease {}
        Ok(Box::new(NullLease))
    }

    async fn did_open(
        &self,
        _workspace_root: &Path,
        _file_path: &Path,
        _content: &str,
    ) -> Result<(), pathfinder_lsp::LspError> {
        Ok(())
    }
    async fn did_change(
        &self,
        _workspace_root: &Path,
        _file_path: &Path,
        _content: &str,
        _version: i32,
    ) -> Result<(), LspError> {
        Ok(())
    }
    async fn did_close(&self, _workspace_root: &Path, _file_path: &Path) -> Result<(), LspError> {
        Ok(())
    }
    async fn pull_diagnostics(
        &self,
        _workspace_root: &Path,
        _file_path: &Path,
    ) -> Result<Vec<LspDiagnostic>, LspError> {
        Err(LspError::UnsupportedCapability {
            capability: "diagnosticProvider".into(),
        })
    }
    async fn collect_diagnostics(
        &self,
        _workspace_root: &Path,
        _file_path: &Path,
        _content: &str,
        _version: i32,
        _timeout_ms: u64,
    ) -> Result<Vec<LspDiagnostic>, LspError> {
        Err(LspError::UnsupportedCapability {
            capability: "diagnosticProvider (push model)".into(),
        })
    }
    async fn pull_workspace_diagnostics(
        &self,
        _workspace_root: &Path,
        _file_path: &Path,
    ) -> Result<Vec<LspDiagnostic>, LspError> {
        Err(LspError::UnsupportedCapability {
            capability: "diagnosticProvider".into(),
        })
    }
    async fn range_formatting(
        &self,
        _workspace_root: &Path,
        _file_path: &Path,
        _start_line: u32,
        _end_line: u32,
        _original_content: &str,
    ) -> Result<Option<String>, LspError> {
        Ok(None)
    }
    async fn capability_status(
        &self,
    ) -> std::collections::HashMap<String, pathfinder_lsp::types::LspLanguageStatus> {
        std::collections::HashMap::new()
    }
    fn missing_languages(&self) -> Vec<pathfinder_lsp::client::MissingLanguage> {
        vec![]
    }
    async fn force_respawn(&self, _language_id: &str) -> Result<(), LspError> {
        Ok(())
    }
    async fn did_change_watched_files(
        &self,
        _changes: Vec<pathfinder_lsp::types::FileEvent>,
    ) -> Result<(), LspError> {
        Ok(())
    }
}

pub(crate) fn make_server_dyn(
    ws_dir: &tempfile::TempDir,
    surgeon: Arc<dyn pathfinder_treesitter::surgeon::Surgeon>,
) -> crate::server::PathfinderServer {
    let ws = WorkspaceRoot::new(ws_dir.path()).expect("valid root");
    let config = PathfinderConfig::default();
    let sandbox = Sandbox::new(ws.path(), &config.sandbox);
    crate::server::PathfinderServer::with_engines(
        ws,
        config,
        sandbox,
        Arc::new(MockScout::default()),
        surgeon,
    )
}

pub(crate) fn make_server(
    ws_dir: &tempfile::TempDir,
    mock_surgeon: MockSurgeon,
) -> crate::server::PathfinderServer {
    make_server_dyn(ws_dir, Arc::new(mock_surgeon))
}

pub(crate) fn make_body_range(
    open: usize,
    close: usize,
    indent: usize,
    body_indent: usize,
) -> BodyRange {
    BodyRange {
        start_byte: open,
        end_byte: close,
        indent_column: indent,
        body_indent_column: body_indent,
    }
}