Skip to content

Commit 4fbfa66

Browse files
authored
feat(config): add server-specific heuristics for spawn filtering (#45)
* feat(config): add server-specific heuristics for spawn filtering Add minimal heuristics to prevent spawning LSP servers in projects where they are not applicable (e.g., rust-analyzer in Python project). - Add ServerHeuristics struct with project_markers field (OR logic) - Extend LspServerConfig with optional heuristics field - Filter servers based on workspace markers before spawn_batch() - Add default heuristics for rust-analyzer, pyright, typescript, gopls, clangd, zls - Add comprehensive unit tests for heuristics logic Servers without heuristics always attempt spawn (backward compatible). Skipped servers are logged at INFO level. Closes #37 * docs: update changelog for server heuristics feature * patch: bump version to 0.3.2 and document server heuristics - Update workspace version from 0.3.1 to 0.3.2 - Update CHANGELOG with release date - Add Server Heuristics section to README documenting: - Default project markers for each language server - OR logic for marker detection - Custom heuristics configuration examples
1 parent e6c99bf commit 4fbfa66

10 files changed

Lines changed: 361 additions & 19 deletions

File tree

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.3.2] - 2026-02-03
9+
10+
### Added
11+
12+
**Server-Specific Heuristics** (fixes #37):
13+
- Add `ServerHeuristics` struct with `project_markers` field for spawn filtering
14+
- Prevent spawning LSP servers in projects where they are not applicable
15+
- OR logic: server spawns if ANY marker file exists in workspace
16+
- Default heuristics for common LSP servers:
17+
- rust-analyzer: `Cargo.toml`, `rust-toolchain.toml`
18+
- pyright: `pyproject.toml`, `setup.py`, `requirements.txt`, `pyrightconfig.json`
19+
- typescript-language-server: `package.json`, `tsconfig.json`, `jsconfig.json`
20+
- gopls: `go.mod`, `go.sum`
21+
- clangd: `CMakeLists.txt`, `compile_commands.json`, `Makefile`, `.clangd`
22+
- zls: `build.zig`, `build.zig.zon`
23+
- User-configurable heuristics via `[lsp_servers.heuristics]` in `mcpls.toml`
24+
- Servers without heuristics always attempt spawn (backward compatible)
25+
- Skipped servers logged at INFO level for debugging
26+
- 12 new unit tests for heuristics logic (329 total tests)
27+
828
## [0.3.1] - 2026-01-24
929

1030
### Added

Cargo.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ members = ["crates/*"]
33
resolver = "2"
44

55
[workspace.package]
6-
version = "0.3.1"
6+
version = "0.3.2"
77
edition = "2024"
88
rust-version = "1.85"
99
authors = ["Andrei G. <k05h31@gmail.com>"]
@@ -24,7 +24,7 @@ clap = "4.5"
2424
dirs = "6.0"
2525
futures = "0.3"
2626
lsp-types = "0.97"
27-
mcpls-core = { path = "crates/mcpls-core", version = "0.3.1" }
27+
mcpls-core = { path = "crates/mcpls-core", version = "0.3.2" }
2828
predicates = "3.1"
2929
rmcp = "0.14"
3030
rstest = "0.26"

README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,47 @@ Claude: [get_references] Found 4 matches:
261261
| `MCPLS_LOG` | Log level (trace, debug, info, warn, error) | `info` |
262262
| `MCPLS_LOG_JSON` | Output logs as JSON | `false` |
263263

264+
### Server Heuristics
265+
266+
mcpls uses smart heuristics to spawn only relevant language servers. Each server checks for project markers before starting — if no markers are found, the server is skipped.
267+
268+
| Language | Server | Project Markers |
269+
|----------|--------|-----------------|
270+
| Rust | rust-analyzer | `Cargo.toml`, `rust-toolchain.toml` |
271+
| Python | pyright | `pyproject.toml`, `setup.py`, `requirements.txt`, `pyrightconfig.json` |
272+
| TypeScript | typescript-language-server | `package.json`, `tsconfig.json`, `jsconfig.json` |
273+
| Go | gopls | `go.mod`, `go.sum` |
274+
| C/C++ | clangd | `CMakeLists.txt`, `compile_commands.json`, `Makefile`, `.clangd` |
275+
| Zig | zls | `build.zig`, `build.zig.zon` |
276+
277+
> [!TIP]
278+
> Heuristics use OR logic — if ANY marker exists, the server spawns. This prevents spawning rust-analyzer in a Python-only project.
279+
280+
#### Custom Heuristics
281+
282+
Override or disable heuristics per server:
283+
284+
```toml
285+
[[lsp_servers]]
286+
language_id = "rust"
287+
command = "rust-analyzer"
288+
file_patterns = ["**/*.rs"]
289+
290+
# Custom markers (OR logic)
291+
[lsp_servers.heuristics]
292+
project_markers = ["Cargo.toml", "rust-toolchain.toml", ".rust-version"]
293+
```
294+
295+
To always spawn a server regardless of project type, omit the `heuristics` section:
296+
297+
```toml
298+
[[lsp_servers]]
299+
language_id = "python"
300+
command = "pyright-langserver"
301+
args = ["--stdio"]
302+
# No heuristics = always spawn
303+
```
304+
264305
### Full Configuration Example
265306

266307
```toml
@@ -275,6 +316,9 @@ args = []
275316
file_patterns = ["**/*.rs"]
276317
timeout_seconds = 30
277318

319+
[lsp_servers.heuristics]
320+
project_markers = ["Cargo.toml", "rust-toolchain.toml"]
321+
278322
[lsp_servers.initialization_options]
279323
cargo.features = "all"
280324
checkOnSave.command = "clippy"

crates/mcpls-core/src/config/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use std::collections::HashMap;
99
use std::path::{Path, PathBuf};
1010

1111
use serde::{Deserialize, Serialize};
12-
pub use server::LspServerConfig;
12+
pub use server::{LspServerConfig, ServerHeuristics};
1313

1414
use crate::error::{Error, Result};
1515

0 commit comments

Comments
 (0)