Skip to content

Add LSP multiplexer to support multiple LSP toolsets#1970

Open
dgageot wants to merge 1 commit intodocker:mainfrom
dgageot:board/multiple-lsp-7f7e9871
Open

Add LSP multiplexer to support multiple LSP toolsets#1970
dgageot wants to merge 1 commit intodocker:mainfrom
dgageot:board/multiple-lsp-7f7e9871

Conversation

@dgageot
Copy link
Member

@dgageot dgageot commented Mar 6, 2026

When multiple LSP toolsets are configured (e.g., gopls for Go and pyright for Python), each produces tools with identical names (lsp_hover, lsp_definition, etc.), causing LLM APIs to reject the request because tool names must be unique.

Changes

  • pkg/tools/builtin/lsp_multiplexer.go — New LSPMultiplexer that combines multiple LSP backends into a single toolset:

    • File-based tools (hover, definition, references, etc.) are routed to the correct backend by matching the file extension via HandlesFile()
    • Non-file tools (workspace, workspace_symbols) are broadcast to all backends with results merged
    • Implements ToolSet, Startable, and Instructable interfaces
    • Deduplicates identical instructions across backends
  • pkg/teamloader/teamloader.go — Modified getToolsForAgent to detect multiple LSP toolsets and combine them into a single multiplexer. Per-toolset config (tool filters, instructions, toon, defer) is preserved.

  • Tests — Comprehensive test coverage for routing, broadcasting, error handling, deduplication, and teamloader integration.

Fixes #1969

@dgageot dgageot requested a review from a team as a code owner March 6, 2026 15:00
Copy link

@docker-agent docker-agent bot left a comment

Choose a reason for hiding this comment

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

Review Summary

Assessment: 🔴 CRITICAL

This PR adds LSP multiplexing functionality to combine multiple LSP toolsets. The core routing logic is sound, but there are 1 high-severity resource leak and 4 medium-severity issues that should be addressed before merging.

Critical Issues

HIGH: Resource leak in Start method - if initialization fails partway through, previously started LSP backends are left running.

Notable Issues

  • Empty file arguments may route to unintended backends
  • Type assertion failures are silently ignored in teamloader
  • Broadcast operations abort on first error instead of aggregating results
  • Constructor panics on invalid input (could return error instead)

Details

See inline comments below.


func (m *LSPMultiplexer) Start(ctx context.Context) error {
for _, b := range m.backends {
if err := b.lsp.Start(ctx); err != nil {
Copy link

Choose a reason for hiding this comment

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

HIGH SEVERITY: Resource leak on partial Start failure

If Start fails on any backend after the first, the previously started backends are not cleaned up. This leaves LSP processes running.

Example scenario:

  • Backend 0 (gopls) starts successfully
  • Backend 1 (pyright) fails to start
  • Function returns error
  • Backend 0 remains running

Recommendation:
Track successfully started backends and call Stop on them before returning the error:

func (m *LSPMultiplexer) Start(ctx context.Context) error {
	var started []lspBackend
	for _, b := range m.backends {
		if err := b.lsp.Start(ctx); err != nil {
			// Clean up previously started backends
			for _, s := range started {
				_ = s.lsp.Stop(ctx)
			}
			return fmt.Errorf("starting LSP backend %q: %w", b.lsp.handler.command, err)
		}
		started = append(started, b)
	}
	return nil
}

@dgageot dgageot force-pushed the board/multiple-lsp-7f7e9871 branch 2 times, most recently from 012c509 to aa55338 Compare March 6, 2026 15:46
When multiple LSP toolsets are configured (e.g., gopls for Go and
pyright for Python), each produces tools with identical names
(lsp_hover, lsp_definition, etc.), causing LLM APIs to reject the
request because tool names must be unique.

Add an LSPMultiplexer that combines multiple LSP backends into a single
toolset. File-based tools (hover, definition, references, etc.) are
routed to the correct backend by matching the file extension.
Non-file tools (workspace, workspace_symbols) are broadcast to all
backends and results are merged.

The teamloader detects multiple LSP toolsets during agent loading and
automatically wraps them in a multiplexer. Per-toolset config (tool
filters, instructions, toon) is preserved through wrapping.

Fixes docker#1969

Assisted-By: cagent
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Multiple LSP toolsets in a single agent cause 'Tool names must be unique' error

1 participant