Expose additional LSP operations: diagnostics, codeAction, rename

Status Open
Maintainer reply None cached
Activity 7 comments · opened Mar 28, 2026

Feature Request

The built-in LSP tool currently exposes 9 operations:

goToDefinition, findReferences, hover, documentSymbol, workspaceSymbol, goToImplementation, prepareCallHierarchy, incomingCalls, outgoingCalls

These are excellent for code navigation, but the LSP protocol has ~40+ capabilities, and several high-value ones are missing. Adding them would significantly improve Claude's ability to understand, validate, and refactor code — especially in typed languages like Java, TypeScript, C#, and Rust.

Requested Operations (by impact)

1. textDocument/diagnostic (pull diagnostics)

Why: Lets Claude check if code compiles and see errors/warnings without running a full build. Currently, the only way to know if a change broke something is to run mvn compile or equivalent — which is slow and noisy. Diagnostics from the LSP are instant since the server is already analyzing the code in the background.

Use case: After editing a Java file, Claude could immediately check "did I break anything?" without a build round-trip.

2. textDocument/codeAction

Why: LSP code actions are context-aware fixes and refactorings: auto-fix imports, implement interface methods, extract variable/method, convert to lambda, etc. These are operations Claude currently does manually (and sometimes gets wrong — e.g., guessing import paths instead of letting the language server resolve them).

Use case: Claude edits a file, a diagnostic shows a missing import → code action resolves the correct fully-qualified import automatically.

3. textDocument/rename

Why: LSP rename is semantic — it understands type hierarchies, updates imports, handles overrides, and renames across the entire workspace. Currently Claude has to use Grep-based find-and-replace, which is fragile (can miss references, rename unrelated symbols with the same name, or break imports).

Use case: Renaming a method on an interface correctly propagates to all implementing classes and call sites.

4. textDocument/signatureHelp (lower priority)

Why: Shows exact parameter types and documentation for a method call. Useful when Claude is writing a call to an API it hasn't read yet.

5. textDocument/typeDefinition (lower priority)

Why: Navigates from a variable to its type declaration (vs goToDefinition which goes to the variable's definition). Useful for understanding what type a variable holds without hovering.

Context

  • These operations are language-agnostic — they'd benefit all LSP-enabled plugins (Java/jdtls, TypeScript, Python/pyright, Go/gopls, Rust/rust-analyzer, etc.)
  • The LSP server is already running and maintaining full project state — these capabilities are "free" to expose since the server already computes them
  • ENABLE_LSP_TOOL=1 suggests the tool is still maturing, so this seems like a natural next step

Environment

  • Claude Code CLI (latest)
  • Using jdtls-lsp plugin (Eclipse JDT.LS 1.57.0)

View original on GitHub ↗

7 Comments

klaidliadon · 5 months ago

+1 — diagnostics and rename are the big ones for Go development. gopls already computes them, just need the LSP tool to expose the operations.

davidfstr · 4 months ago

+1 to add an official "rename" tool, in particular.

I currently use the "rename_symbol" tool from my own revise-mcp server but would love a first-party implementation in Claude Code or its LSP server.

FWIW, VS Code Copilot Agent somewhat recently - in VS Code 1.110 (February 2026) - added its own official "rename" tool.

saturnxxi · 4 months ago

+1 for rename and codeAction. I have an Intelephense Premium license specifically for these features, but Claude Code's LSP tool only exposes 9 operations. rename would be especially valuable — it's symbol-aware and safer than grep+edit for refactoring. Currently there's no built-in way to leverage paid LSP features like this.

oxysoft · 4 months ago

I have implemented a MCP server that LSP plugins can use to automatically provide tools for missing unimplemented features, which were perplexingly left out by the developers for reasons we cannot explain. Since Anthropic does not communicate clearly, it seems that this is done by a products management or marketing team, for example creating an artificial release as "We shipped enhanced support for LSP!" I have implemented a LSP plugin using ty for python which uses the MCP shim.

Using this shim, LSP plugins can ship with a cc hook that forbids the builtin LSP tool and replace the output with a redirection banner requesting Claude to use our powerful interface.

This provides the rename tool. The tools also do not require passing line:col arguments which is simply incorrect for this interface context. Instead, Claude provides a line number and a text match. The MCP plugin fetches the correct

  1. https://github.com/holo-q/cc-lsp-now: use LSP now at their peak in claude code
  2. https://github.com/holo-q/cc-lsp-ty: ty plugin built on cc-lsp-now

Features

  • rename, codeAction, signatureHelp, typeDefinition, callHierarchy, completion, formatting — all missing from the built-in
  • Symbol-name addressing: lsp_hover(file, symbol="MyClass") instead of lsp_hover(file, line=133, col=7). Models don't think in cursor columns.
  • Multi-target batching: symbols="Foo,Bar,Baz" or file_path="a.py,b.py"
  • Fallback chain: when the primary server doesn't implement a method, automatically routes to a secondary server, per-method cached
  • Provenance headers: [ty textDocument/hover] so the model sees which server handled each request

---

Over the course of this implementation, Claude Opus 4.7 itself said that the builtin support is 'trash', no holding back or hedging. This is shocking since this can scale capabilities dramatically and is the first thing a harness needs. Perhaps it would reveal that the previous models were more useful than previously thought, and that the new model is not their "most capable yet" but rather "evaluated on a more capable harness."

I am understandingly upset. The Anthropic developers repeatedly do not address low-hanging fruits, and when we bring up those things, it's all crickets. We could implement it ourselves, but the harness is closed source, even though this isn't a moat and is all basic plumbing. This is a symptom of a much more serious problem happening at this company and nobody seems to notice. Again, these are things that Anthropic must address, otherwise we are left to assume the most basic explanation.

Thank you

t0k4rt · 3 months ago

+1 for rename,
So weird it's not already supported

technicalpickles · 3 months ago

Ran into this trying to figure out why the official ruby-lsp plugin produces zero diagnostics in Claude Code. Did a full out-of-process investigation; the relevant bit for this thread:

ruby-lsp 0.26.9 (current latest) ships exactly one outbound textDocument/publishDiagnostics call in the entire codebase. It's in text_document_did_close, where it sends an empty array to clear stale diagnostics for the closing file. That's the whole push surface. Same on main HEAD. Everything else, edits, opens, saves, lives behind textDocument/diagnostic (pull).

Confirmed by writing a small LSP JSON-RPC stdio driver and pointing it at a Ruby file with 11 deliberate rubocop offenses (LineLength 200+ chars, ParameterLists with 8 args, single-letter param names):

  • didOpen + 15s listening window: zero notifications
  • didChange + 15s listening window: zero notifications
  • textDocument/diagnostic (pull): all 11 offenses come back, severity + ranges + autocorrect data intact

This is intentional. Shopify/ruby-lsp#242 (merged August 2022) deliberately switched from push to pull-only. The PR title is literally "Let clients pull diagnostics instead of pushing on edits." Rationale was performance: pull decouples diagnostics from didChange and lets the client debounce.

When someone hit the same wall with Nova in Shopify/ruby-lsp#1873, @vinistock (ruby-lsp maintainer) was direct:

We switched to using pull diagnostics because it allowed us to improve the way we lazy parse documents in the server. [...] pull diagnostics were added to the LSP spec a while ago, so it might make sense for [the client] to support it.

NeoVim users hit it too; the official ruby-lsp docs now point them at a community plugin (pull_diags.nvim) to add pull support on the client side.

So any LSP server that's migrated to pull-only since LSP 3.17 (March 2022) is invisible to Claude Code's diagnostic enrichment today. ruby-lsp is the most prominent one, list is growing. Implementing this feature request is the only realistic path forward for those servers, since upstream has been clear they're not adding push back.

Happy to share the driver script if it's useful for repro.

technicalpickles · 3 months ago

Drove the same JSON-RPC stdio driver against gopls, typescript-language-server, and rust-analyzer to see how widely the missing-diagnostics gap actually applies. LSP 3.17 added pull diagnostics alongside push, so any of these servers could be on either side.

It's a mixed bag.

gopls v0.22.0 (go1.26.1) is push-comprehensive. publishDiagnostics fires on didOpen, didChange, and didSave. diagnosticProvider isn't advertised, but textDocument/diagnostic actually works anyway (returns kind: "", which is off-spec, but the items come back). Push and pull carry the same content. Claude Code's existing receive-push path works fine here.

typescript-language-server 5.2.0 (bundled tsserver 6.0.3) is push-only. Fires publishDiagnostics on didOpen and didChange. textDocument/diagnostic returns -32601 Unhandled method. Pull doesn't exist here at all. Push content is complete (all 8 TS errors with source: "typescript", codes 2322/2345/6133). Claude Code is fine here too.

rust-analyzer 0.3.2904 (2026-05-17) is the interesting one. It advertises both push and pull, both work, but they carry different content.

Drove it against a tiny cargo crate with:

  • use std::collections::HashMap; (unused import)
  • fn BadlyNamed() (non_snake_case)
  • let x: i32 = "not a number"; (E0308)
  • After didChange: fn AnotherBadName(unused_param: i32) -> i32 { unused_param + "oops" }

Per-phase counts:

didOpen:   2 publishDiagnostics events  -> cargo-check output, source: "rustc"
didChange: 0 events
didSave:   2 publishDiagnostics events  -> same as didOpen, re-runs flycheck
Pull:      3 items                      -> rust-analyzer's own lints, source: "rust-analyzer"

Push channel returned: rustc:unused_imports, rustc:E0308.
Pull channel returned: rust-analyzer:non_snake_case (x2), rust-analyzer:E0308.

Only E0308 shows up in both. Everything else is one-channel-only. A push-receive client gets cargo-check warnings and errors but never sees rust-analyzer's native lints (non_snake_case, dead_code patterns, and friends).

rust-analyzer also fires workspace/diagnostic/refresh (twice in my run), which is the server telling clients to re-pull. A client that only listens for push silently ignores that and never re-fetches the pull-only diagnostics. So even when push works, you lose freshness on the native lint side.

Net of the four servers:

| server | push works? | pull needed? |
|---|---|---|
| ruby-lsp | no | yes, for all diagnostics |
| rust-analyzer | partially | yes, for native lints |
| gopls | yes | no |
| typescript-language-server | yes | no |

Adding pull-diagnostic support to Claude Code:

  • fully fixes ruby-lsp (currently zero diagnostics from the server side)
  • recovers rust-analyzer's native lints (currently invisible to push-only clients)
  • gopls and typescript-language-server keep getting push, no change for them

Worth surveying clangd, pyright/basedpyright, and vtsls at some point too. Happy to share the driver scripts if anyone wants to repro or extend.