Skip to content

feat(config): explicit per-tool routing for shared-language LSP servers#228

Merged
bug-ops merged 3 commits into
mainfrom
feat/issue-174/explicit-tool-routing
Jul 25, 2026
Merged

feat(config): explicit per-tool routing for shared-language LSP servers#228
bug-ops merged 3 commits into
mainfrom
feat/issue-174/explicit-tool-routing

Conversation

@bug-ops

@bug-ops bug-ops commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add name and handles fields to [[lsp_servers]] so two servers sharing a language_id (e.g. pyright for navigation, pylsp for diagnostics) can each own a distinct subset of MCP tools instead of one silently shadowing the other.
  • Introduce ServerId, ToolKind, and ToolRouter to resolve (language, tool) -> server, validate routing conflicts at startup (over applicable configs per workspace, not globally), and rebind dead routes to a live catch-all after server registration completes.
  • Fix the document-sync and diagnostics-cache paths so a single file routed to two different servers gets didOpen/didChange on both and doesn't clobber the diagnostics cache.
  • Consolidate the React language-ID mapping (typescript/tsx, javascript/jsx variants) into a single source of truth shared by config extension mapping and translator dispatch, replacing two independent hardcoded match arms that could silently drift apart.

Closes #174, closes #165

Breaking changes (pre-1.0, documented in CHANGELOG.md under [Unreleased]): Error::ServerInitializing/Error::NoServerForTool, Translator's ServerId-keyed maps and renamed set_expected_servers, DocumentTracker::ensure_open's new server: &ServerId parameter, lsp::ServerInitResult.servers keying, and workspace_symbol_search dispatch order (now config declaration order instead of HashMap iteration order). Two servers sharing a language_id with no disambiguating routing now hard-fails at startup instead of silently dropping one.

Test plan

  • cargo +nightly fmt --all -- --check
  • cargo clippy --all-targets --all-features --workspace -- -D warnings
  • cargo nextest run --workspace --all-features (467/467 passing)
  • RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features (rustdoc gate)
  • New integration tests: dispatch-level routing (pyright/pylsp/rename → NoServerForTool), register_servers rebind-before-diagnostics-flags regression, ServerConfig::validate() rules, mutually-exclusive-heuristics fixture (loads when only one server is applicable, fails at startup when both are)

…ervers

Add name and handles fields to [[lsp_servers]] so two servers sharing a
language_id (e.g. pyright for navigation, pylsp for diagnostics) can each
own a distinct subset of MCP tools instead of one silently shadowing the
other. Introduces ServerId, ToolKind, and ToolRouter to resolve
(language, tool) to a server, validate routing conflicts at startup, and
rebind dead routes to a live catch-all after registration. Also
consolidates the React language-ID mapping (typescript/tsx and
javascript/jsx variants) into a single source of truth shared by config
extension mapping and translator dispatch, replacing two independent
hardcoded match arms.

Closes #174, #165
@github-actions github-actions Bot added documentation Improvements or additions to documentation rust Rust code changes testing Test-related changes mcpls-core mcpls-core crate changes labels Jul 25, 2026
@bug-ops
bug-ops requested a review from Copilot July 25, 2026 01:16

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends mcpls’s configuration and bridge layers to support explicit per-tool routing when multiple LSP servers target the same language/files, while also tightening correctness around multi-server document sync and consolidating React language-id mapping.

Changes:

  • Add name + handles to [[lsp_servers]], plus new routing types (ServerId, ToolKind, ToolRouter) to resolve (language, tool) → server and validate ambiguity at startup.
  • Refactor Translator, server init/registration, diagnostics pumping, and DocumentTracker so the same file can be synced to multiple servers without clobbering caches.
  • Create a single shared React language-id mapping source (config/language.rs) used by both config extension mapping and translator fallback routing.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
examples/mcpls.toml Adds a commented example showing name/handles usage for two Python servers.
docs/user-guide/configuration.md Documents name/handles semantics, valid routing tool names, and startup ambiguity rules.
crates/mcpls-core/tests/integration/rust_analyzer_tests.rs Updates integration setup to install a router and use ServerId.
crates/mcpls-core/tests/integration/basic_tests.rs Adds fixture-based tests for routing + mutually-exclusive heuristics applicability.
crates/mcpls-core/tests/fixtures/configs/two_server_routing.toml New fixture config demonstrating split routing for Python servers.
crates/mcpls-core/tests/fixtures/configs/mutually_exclusive_heuristics.toml New fixture config demonstrating workspace-scoped applicability rules.
crates/mcpls-core/src/lsp/lifecycle.rs Keys server init results by ServerId and threads IDs into spawn failure reporting/tests.
crates/mcpls-core/src/lib.rs Builds a workspace-scoped router, registers servers by ServerId, and gates diagnostics caching per server.
crates/mcpls-core/src/error.rs Adds NoServerForTool, changes ServerInitializing to carry ServerId, and enriches spawn failure display.
crates/mcpls-core/src/config/server.rs Extends LspServerConfig with name/handles and adds id() derivation.
crates/mcpls-core/src/config/routing.rs New routing module implementing typed tool routing, validation, and post-spawn rebind behavior.
crates/mcpls-core/src/config/mod.rs Wires in routing + React mapping modules and adds validation for name/handles.
crates/mcpls-core/src/config/language.rs New single source of truth for React language-id variants and inverse mapping.
crates/mcpls-core/src/bridge/translator.rs Routes tool calls via ToolRouter, keys client/server maps by ServerId, and applies React fallback consistently.
crates/mcpls-core/src/bridge/state.rs Tracks per-server sync state (synced) so one path can be didOpen/didChange-synced independently to multiple servers.
CHANGELOG.md Documents the new routing feature and breaking API changes.

Comment thread CHANGELOG.md Outdated
Comment thread crates/mcpls-core/src/bridge/state.rs Outdated
Comment thread crates/mcpls-core/src/bridge/state.rs
Comment thread crates/mcpls-core/src/bridge/state.rs
The unit/integration/e2e test steps never passed --profile ci, so the
retries=2 and 180s slow-timeout already defined in .config/nextest.toml
for CI went unused; every run fell back to the default profile's 120s
ceiling with no retries. This PR roughly doubled the fake_lsp_client()
subprocess-backed tests in bridge/state.rs, and on the two-core
windows-latest runner the ones that wait on actual child-process wire
I/O started tripping that ceiling.
@github-actions github-actions Bot added the ci/cd CI/CD pipeline changes label Jul 25, 2026
disk_phase and disk_phase_new checked a file's size against a
separately-stat'd metadata() call, then read the same path again via a
new open. A concurrent atomic replace between those two calls could
substitute a larger file after the size gate passed, defeating the
pre-read size limit. read_to_string_checked now opens the file once and
stats/reads through that single handle, so the size gate and the read
observe the same file consistently.

Also corrects the CHANGELOG's Translator API entry, which named a
router() accessor that was never added (the actual methods are
with_router, rebind_router, and is_diagnostics_route).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd CI/CD pipeline changes documentation Improvements or additions to documentation mcpls-core mcpls-core crate changes rust Rust code changes testing Test-related changes

Projects

None yet

2 participants