Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 18 additions & 10 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.3.8] - 2026-07-27

### Added

- **`--trust-project-config` flag / `MCPLS_TRUST_PROJECT_CONFIG` env var** — opt-in gate for loading a `./mcpls.toml` discovered relative to the current directory. New `config::ProjectConfigTrust` enum and `ServerConfig::load_with_trust(trust)`, which `ServerConfig::load()` now delegates to with `ProjectConfigTrust::Untrusted`. (#229)
Expand All @@ -24,6 +26,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Two servers sharing a `language_id` with no disambiguating routing now hard-fails at `serve_with` startup** — previously the second server silently overwrote the first in every map keyed by language; `serve_with` now returns a startup error naming the conflicting `[[lsp_servers]]` entries instead. `ServerConfig::load`/`load_from` (parsing) is unaffected — this check runs only over the applicable configs for a given workspace, so a config with mutually exclusive `heuristics.project_markers` (only one of which is ever applicable in a given workspace) still loads and starts. (#174)
- **`workspace_symbol_search` dispatch order** — changed from `HashMap` iteration order (nondeterministic across runs) to config declaration order via `ToolRouter::resolve_any`'s three-tier resolution (explicit claimer, then catch-all, then first live server). (#174)
- **`Translator::handle_diagnostics` signature** — Breaking change: now takes an additional `notification_cache: &tokio::sync::Mutex<NotificationCache>` parameter, needed for the flycheck-merge fix below. Acceptable pre-1.0. (#244)
- **`DocumentState` API** — Breaking change: `DocumentState` gains a `disk: Option<DiskSync>` field tracking the filesystem snapshot behind the resync mechanism below; existing struct-literal construction must add this field. Acceptable pre-1.0. (#102)
- **`rmcp` 2.2.0** — Breaking change upstream: bump `rmcp` from 1.8.0 to 2.2.0 to align with the MCP 2025-11-25 spec. `rmcp::model::RawResource` and the `Annotated<RawResource>` wrapper were merged into a single flat `rmcp::model::Resource` struct; `McplsServer::list_resources` updated accordingly.
- **`McplsServer::new` / `HandlerContext::new` signatures** — Breaking change: both now take an additional `Arc<Mutex<NotificationCache>>` and an `Arc<[PathBuf]>` workspace-roots snapshot, alongside the existing translator and subscriptions. `Translator::handle_cached_diagnostics` was removed and replaced by `Translator::cached_diagnostics_uri(workspace_roots, file_path) -> Result<String>` and `Translator::diagnostics_from_cache_entry(Option<&DiagnosticInfo>) -> DiagnosticsResult`, so callers control exactly how long the `NotificationCache` lock is held between the two; `Translator::handle_server_logs`/`handle_server_messages` became associated functions taking `&NotificationCache` directly, since neither needed translator state. Acceptable pre-1.0. (#104)
- **`HandlerContext` renamed to `BridgeContext`; `translator` field/parameter no longer `Mutex`-wrapped** — Breaking change: `HandlerContext` is renamed `BridgeContext`, and its `translator` field (plus the corresponding `McplsServer::new` parameter) changed from `Arc<Mutex<Translator>>` to `Arc<Translator>`. `Translator` now manages its own per-field locking internally (see the lock-contention fix below), so the outer mutex is gone; all `Translator::handle_*` methods take `&self` instead of `&mut self`. Acceptable pre-1.0. (#108, #159)
- Bump anyhow from 1.0.102 to 1.0.104
- Bump async-trait from 0.1.89 to 0.1.91
- Bump ignore from 0.4.26 to 0.4.31
- Bump toml from 1.1.2 to 1.1.3
- CI: bump actions/checkout from 6 to 7
- CI: bump EmbarkStudios/cargo-deny-action from 2.0.20 to 2.1.1
- CI: bump cargo-bins/cargo-binstall from 1.20.0 to 1.21.0
- CI: bump softprops/action-gh-release from 3.0.0 to 3.0.2
- CI: bump dorny/paths-filter from 4.0.1 to 4.0.2
- CI: bump lewagon/wait-on-check-action from 1.8.0 to 1.8.1

### Fixed

Expand All @@ -36,16 +52,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **`get_cached_diagnostics` still held the cache lock across `canonicalize()` and result mapping** — the `Translator::handle_cached_diagnostics` split above narrowed but didn't fully close the same lock-contention class: path validation (a filesystem `canonicalize()` syscall) and the cached-diagnostics-to-MCP-shape mapping still ran while holding the `notification_cache` mutex that `diagnostics_pump` also needs. Split into `Translator::cached_diagnostics_uri` (path validation, no lock involved) and `Translator::diagnostics_from_cache_entry` (pure mapping over an already-cloned entry, no lock involved); `get_cached_diagnostics` now holds the cache lock only for the `get_diagnostics(&uri).cloned()` lookup itself. (#104)
- **`read_resource` used the raw, non-canonicalized path to build its diagnostics-cache URI** — `validate_path_against_roots` returns the canonicalized path but the result was discarded, so `read_resource` looked up the cache under the client's original (possibly symlinked or `..`-containing) path instead of the canonical form `diagnostics_pump` actually stores diagnostics under, causing spurious cache misses for otherwise-valid, tracked files. Fixed to build the URI from the validated/canonical path, matching `get_cached_diagnostics`. (#104)
- **`get_diagnostics` silently omitted flycheck/clippy-sourced diagnostics, and lost cached diagnostics entirely on a pull failure** — `Translator::handle_diagnostics` relied exclusively on the LSP pull model (`textDocument/diagnostic`); diagnostics sourced from rust-analyzer's background flycheck process (`cargo check`/clippy — e.g. `unused_imports`, `dead_code`, clippy lints), and empirically some native diagnostics too, are delivered solely via `textDocument/publishDiagnostics` push notifications and were already cached by `NotificationCache`, but `get_diagnostics` never consulted it, unlike `get_cached_diagnostics`. `handle_diagnostics` now takes the shared `NotificationCache` and merges its entry for the file into the pull result (new `Translator::merge_diagnostics`); if the pull request itself errors (e.g. a push-only server, or a timeout), a non-empty cache entry is now returned instead of the error. Deduplication requires matching `(severity, code)` *and* range proximity (overlapping ranges, or start positions within a few lines of each other) rather than full field equality: verified empirically against a live rust-analyzer, the pull and push representations of the same logical diagnostic can carry different `range` and rendered `message`, so exact-equality dedup would have reported it twice — while a code-only key would have silently merged two distinct, unrelated diagnostics that happen to share an error code (e.g. two separate `E0308` mismatches at different call sites). The cache is treated as eventually consistent, same as `get_cached_diagnostics`. (#244)

### Changed

- **`DocumentState` API** — Breaking change: `DocumentState` gains a `disk: Option<DiskSync>` field tracking the filesystem snapshot behind the resync mechanism above; existing struct-literal construction must add this field. Acceptable pre-1.0. (#102)
- **`rmcp` 2.2.0** — Breaking change upstream: bump `rmcp` from 1.8.0 to 2.2.0 to align with the MCP 2025-11-25 spec. `rmcp::model::RawResource` and the `Annotated<RawResource>` wrapper were merged into a single flat `rmcp::model::Resource` struct; `McplsServer::list_resources` updated accordingly.
- **`McplsServer::new` / `HandlerContext::new` signatures** — Breaking change: both now take an additional `Arc<Mutex<NotificationCache>>` and an `Arc<[PathBuf]>` workspace-roots snapshot, alongside the existing translator and subscriptions. `Translator::handle_cached_diagnostics` was removed and replaced by `Translator::cached_diagnostics_uri(workspace_roots, file_path) -> Result<String>` and `Translator::diagnostics_from_cache_entry(Option<&DiagnosticInfo>) -> DiagnosticsResult`, so callers control exactly how long the `NotificationCache` lock is held between the two; `Translator::handle_server_logs`/`handle_server_messages` became associated functions taking `&NotificationCache` directly, since neither needed translator state. Acceptable pre-1.0. (#104)
- **`HandlerContext` renamed to `BridgeContext`; `translator` field/parameter no longer `Mutex`-wrapped** — Breaking change: `HandlerContext` is renamed `BridgeContext`, and its `translator` field (plus the corresponding `McplsServer::new` parameter) changed from `Arc<Mutex<Translator>>` to `Arc<Translator>`. `Translator` now manages its own per-field locking internally (see the lock-contention fix above), so the outer mutex is gone; all `Translator::handle_*` methods take `&self` instead of `&mut self`. Acceptable pre-1.0. (#108, #159)

### Fixed

- **`subscribe()` missed diagnostics already cached before the subscription, and could silently stop pushing updates for non-canonical URIs** — a client subscribing to an `lsp-diagnostics://` URI after the LSP server had already pushed its first `publishDiagnostics` for that file saw nothing until the next push; `subscribe()` now checks the notification cache and immediately replays a `resources/updated` notification when diagnostics are already cached for the URI (the subscription is recorded before the cache check, so at worst a harmless duplicate notification occurs, never a dropped one). Separately, `subscribe`/`unsubscribe` tracked the client's raw request URI while `diagnostics_pump` matches against the canonical LSP path, so a client subscribing with a non-canonical but valid URI (symlink, macOS `/var` vs `/private/var`) received the initial replay but silently stopped receiving further pushes; both now key off the canonicalized path from `validate_path_against_roots`, with `unsubscribe` falling back to the raw URI if the file no longer exists. (#131)
- **`path_to_uri` produced invalid `file://` URIs for paths containing RFC 3986 reserved characters** — filesystem paths containing `[`, `]`, `^`, or `|` (e.g. Next.js dynamic route files like `[...slug].ts`) were embedded in LSP `file://` URIs unencoded; these characters are now percent-encoded (`%5B`, `%5D`, `%5E`, `%7C`), and `uri_to_path` decodes them back to the original path on the return trip. (#151)
- **RUSTSEC-2026-0204** — bump transitive `crossbeam-epoch` dependency (pulled in via `ignore`) from 0.9.18 to 0.9.20 to resolve an invalid pointer dereference in `fmt::Pointer` impls for `Atomic`/`Shared`
- **TSX/JSX diagnostics** — Preserve `typescriptreact`/`javascriptreact` language IDs when deriving mappings from TypeScript/JavaScript server `file_patterns`, fixing JSX parse errors when one server handles both plain and React extensions. (#148)

Expand Down
Loading
Loading