Skip to content

fix(install): unwrap lspServers envelope in plugin .lsp.json (closes #1683)#1686

Open
sergio-sisternes-epam wants to merge 2 commits into
mainfrom
sergio-sisternes-epam/fix-lsp-json-unwrap-lspservers-envelope
Open

fix(install): unwrap lspServers envelope in plugin .lsp.json (closes #1683)#1686
sergio-sisternes-epam wants to merge 2 commits into
mainfrom
sergio-sisternes-epam/fix-lsp-json-unwrap-lspservers-envelope

Conversation

@sergio-sisternes-epam
Copy link
Copy Markdown
Collaborator

TL;DR

Plugin .lsp.json files using the { "lspServers": { ... } } wrapper format are now correctly unwrapped during install, instead of being silently skipped with a misleading validation error.

Problem

When a plugin ships a .lsp.json using the standard wrapper format:

{
  "lspServers": {
    "my-lsp-server": {
      "command": "my-lsp",
      "args": ["--stdio"],
      "extensionToLanguage": { ".ext": "mylang" }
    }
  }
}

_read_lsp_json() returned the parsed JSON verbatim, causing "lspServers" to be treated as a server name. Its nested dict value (containing the actual servers) was then treated as that server's config -- which lacks a command field, producing:

[!] Skipping invalid LSP server 'lspServers' from plugin 'my-plugin': LSP dependency 'lspServers' requires 'command'

The real LSP servers were never written to .github/lsp.json.

Fix

In _read_lsp_json() (src/apm_cli/deps/plugin_parser.py), after parsing the JSON dict, detect whether it contains a "lspServers" key with a dict value. If so, return the inner dict (the actual server map). The flat format (server names as top-level keys) continues to work unchanged.

This mirrors the envelope handling already used by LSPIntegrator._write_target_config() when writing to Copilot and Claude config files.

Changes

File Change
src/apm_cli/deps/plugin_parser.py 3-line envelope unwrap + updated docstring
tests/unit/deps/test_plugin_parser_lsp.py 4 new tests

Tests added

  • test_unwraps_lsp_servers_envelope -- wrapped format returns inner servers
  • test_flat_format_still_works -- flat format regression guard
  • test_auto_discovery_with_lsp_servers_wrapper -- auto-discovered .lsp.json with wrapper
  • test_wrapped_lsp_json_produces_valid_deps -- end-to-end: wrapped file -> valid deps

Validation

  • 20/20 tests pass in tests/unit/deps/test_plugin_parser_lsp.py
  • ruff check + ruff format --check clean
  • pylint R0801 (duplication) clean

Fixes #1683

When a plugin ships a .lsp.json using the { "lspServers": { ... } }
wrapper format, _read_lsp_json() now detects and unwraps the envelope
instead of treating "lspServers" as a server name.

This fixes the silent skip with:
  Skipping invalid LSP server 'lspServers': requires 'command'

The flat format (server names as top-level keys) continues to work
unchanged.

Fixes #1683

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 6, 2026 22:56
@sergio-sisternes-epam sergio-sisternes-epam added the panel-review Trigger the apm-review-panel gh-aw workflow label Jun 6, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 fixes apm install handling of plugin-provided .lsp.json files by supporting the common { "lspServers": { ... } } wrapper format, so wrapped LSP server definitions are correctly discovered and converted into dependencies instead of being misinterpreted and skipped.

Changes:

  • Update _read_lsp_json() to unwrap the "lspServers" envelope when present.
  • Expand unit tests to cover wrapped .lsp.json parsing, auto-discovery behavior, and end-to-end conversion into valid LSP dependencies.

Reviewed changes

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

File Description
src/apm_cli/deps/plugin_parser.py Adds unwrap logic for the "lspServers" envelope and updates the docstring to document both supported formats.
tests/unit/deps/test_plugin_parser_lsp.py Adds new tests covering wrapped parsing, auto-discovery with wrapper, and end-to-end dependency generation.

Comment thread src/apm_cli/deps/plugin_parser.py
Comment thread tests/unit/deps/test_plugin_parser_lsp.py
Only unwrap the envelope when all values in the inner dict are dicts
(i.e. it looks like a server map). A flat-format server literally
named 'lspServers' would have scalar values like 'command', so the
all-dicts check avoids mis-detecting it as an envelope.

Adds a regression test for this ambiguous edge case.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

panel-review Trigger the apm-review-panel gh-aw workflow

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Plugin .lsp.json with lspServers wrapper key is silently skipped during install

2 participants