lsp-mcp 0.1.0

MCP server providing unified access to Language Server Protocol features
Documentation
use crate::error::Language;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Configuration for a language server
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LanguageServerConfig {
    /// Display name
    pub name: String,
    /// Command to start the server
    pub command: String,
    /// Command arguments
    pub args: Vec<String>,
    /// Environment variables
    #[serde(default)]
    pub env: HashMap<String, String>,
    /// File extensions this server handles
    pub file_extensions: Vec<String>,
    /// Language identifier for LSP
    pub language_id: String,
    /// Initialization options (JSON)
    #[serde(default)]
    pub initialization_options: Option<serde_json::Value>,
    /// Request timeout in seconds
    #[serde(default = "default_timeout")]
    pub timeout_secs: u64,
}

fn default_timeout() -> u64 {
    30
}

/// Registry of known language server configurations
#[derive(Debug, Clone)]
pub struct LanguageServerRegistry {
    configs: HashMap<Language, LanguageServerConfig>,
}

impl Default for LanguageServerRegistry {
    fn default() -> Self {
        Self::new()
    }
}

impl LanguageServerRegistry {
    /// Create a new registry with built-in configurations
    pub fn new() -> Self {
        let mut configs = HashMap::new();

        // Rust - rust-analyzer
        configs.insert(
            Language::Rust,
            LanguageServerConfig {
                name: "rust-analyzer".to_string(),
                command: "rust-analyzer".to_string(),
                args: vec![],
                env: HashMap::new(),
                file_extensions: vec!["rs".to_string()],
                language_id: "rust".to_string(),
                initialization_options: None,
                timeout_secs: 30,
            },
        );

        // TypeScript/JavaScript - typescript-language-server
        configs.insert(
            Language::TypeScript,
            LanguageServerConfig {
                name: "typescript-language-server".to_string(),
                command: "typescript-language-server".to_string(),
                args: vec!["--stdio".to_string()],
                env: HashMap::new(),
                file_extensions: vec![
                    "ts".to_string(),
                    "tsx".to_string(),
                    "js".to_string(),
                    "jsx".to_string(),
                ],
                language_id: "typescript".to_string(),
                initialization_options: None,
                timeout_secs: 30,
            },
        );

        // JavaScript (shares config with TypeScript server)
        configs.insert(
            Language::JavaScript,
            LanguageServerConfig {
                name: "typescript-language-server".to_string(),
                command: "typescript-language-server".to_string(),
                args: vec!["--stdio".to_string()],
                env: HashMap::new(),
                file_extensions: vec!["js".to_string(), "jsx".to_string(), "mjs".to_string()],
                language_id: "javascript".to_string(),
                initialization_options: None,
                timeout_secs: 30,
            },
        );

        // Python - pyright
        configs.insert(
            Language::Python,
            LanguageServerConfig {
                name: "pyright".to_string(),
                command: "pyright-langserver".to_string(),
                args: vec!["--stdio".to_string()],
                env: HashMap::new(),
                file_extensions: vec!["py".to_string(), "pyi".to_string()],
                language_id: "python".to_string(),
                initialization_options: None,
                timeout_secs: 30,
            },
        );

        // Go - gopls
        configs.insert(
            Language::Go,
            LanguageServerConfig {
                name: "gopls".to_string(),
                command: "gopls".to_string(),
                args: vec!["serve".to_string()],
                env: HashMap::new(),
                file_extensions: vec!["go".to_string()],
                language_id: "go".to_string(),
                initialization_options: None,
                timeout_secs: 30,
            },
        );

        // C/C++ - clangd
        configs.insert(
            Language::Cpp,
            LanguageServerConfig {
                name: "clangd".to_string(),
                command: "clangd".to_string(),
                args: vec![],
                env: HashMap::new(),
                file_extensions: vec![
                    "cpp".to_string(),
                    "cc".to_string(),
                    "cxx".to_string(),
                    "hpp".to_string(),
                    "h".to_string(),
                ],
                language_id: "cpp".to_string(),
                initialization_options: None,
                timeout_secs: 30,
            },
        );

        configs.insert(
            Language::C,
            LanguageServerConfig {
                name: "clangd".to_string(),
                command: "clangd".to_string(),
                args: vec![],
                env: HashMap::new(),
                file_extensions: vec!["c".to_string(), "h".to_string()],
                language_id: "c".to_string(),
                initialization_options: None,
                timeout_secs: 30,
            },
        );

        Self { configs }
    }

    /// Get configuration for a language
    pub fn get(&self, language: Language) -> Option<&LanguageServerConfig> {
        self.configs.get(&language)
    }

    /// Add or update a language server configuration
    pub fn register(&mut self, language: Language, config: LanguageServerConfig) {
        self.configs.insert(language, config);
    }

    /// List all registered languages
    pub fn languages(&self) -> impl Iterator<Item = &Language> {
        self.configs.keys()
    }
}