LSP Tool (Code Intelligence)

What Is LSP?

The Search Tools find text patterns. The LSP tool understands what the text means. LSP — Language Server Protocol — connects Amplifier to a language server that has parsed your code into a full semantic model: types, definitions, call chains, references. When you ask "who calls this function?" or "what type does this variable have?", LSP gives you the precise, compiler-grade answer that grep can only approximate.

Amplifier currently configures LSP for Python via the Pyright language server. All operations use 1-based line and character positions — line 1 is the first line, character 1 is the first column.

Core Capabilities

LSP operations fall into three groups: navigation, verification, and refactoring.

goToDefinition — jump to where a symbol is defined:

> Where is the process_data function defined?

[Tool: LSP] goToDefinition  file: src/api/handlers.py  line: 12  character: 15
  src/core/pipeline.py  line 45, character 1

findReferences — find every place a symbol is used:

> Show me everywhere that validate_input is referenced

[Tool: LSP] findReferences  file: src/utils.py  line: 8  character: 5
  src/api/handlers.py    line 22, character 12
  src/api/middleware.py   line 45, character 8
  tests/test_utils.py    line 15, character 20
  (3 references)

hover — get type information, signatures, and docstrings:

> What type is the result variable on line 30?

[Tool: LSP] hover  file: src/main.py  line: 30  character: 5
  (variable) result: Dict[str, List[Item]]

documentSymbol — list all symbols defined in a file:

> What's defined in src/models.py?

[Tool: LSP] documentSymbol  file: src/models.py
  class User (line 8)
  class Order (line 34)
  function create_user (line 62)
  function validate_order (line 78)

prepareCallHierarchy, incomingCalls, outgoingCalls — trace the call graph in either direction. prepareCallHierarchy sets up the target, then incomingCalls shows who calls it and outgoingCalls shows what it calls:

> Who calls the authenticate function?

[Tool: LSP] incomingCalls  file: src/auth.py  line: 15  character: 5
  login_handler        src/api/routes.py     line 42
  refresh_token        src/api/routes.py     line 67
  middleware_check      src/middleware.py      line 23
> What does authenticate call internally?

[Tool: LSP] outgoingCalls  file: src/auth.py  line: 15  character: 5
  verify_password      src/auth.py           line 44
  load_user            src/models.py         line 18
  create_session       src/sessions.py       line 12

Verification

diagnostics — get compiler errors and warnings for a file. This is especially valuable after editing code to confirm you haven't introduced problems:

> Check src/main.py for errors after that refactor

[Tool: LSP] diagnostics  file: src/main.py
  line 42, character 15: error — Argument of type "str" cannot be assigned to parameter "count" of type "int"
  line 67, character 8: warning — Variable "result" is not accessed

Run diagnostics after every significant edit. It catches type mismatches, missing imports, and undefined names before you ever run the code.

Refactoring

rename — semantic cross-file rename. Returns the edits needed but doesn't apply them, so you can review first:

> Rename process_data to transform_records everywhere

[Tool: LSP] rename  file: src/core/pipeline.py  line: 45  character: 5  newName: transform_records
  src/core/pipeline.py      line 45: process_data → transform_records
  src/api/handlers.py       line 22: process_data → transform_records
  tests/test_pipeline.py    line 8:  process_data → transform_records
  (3 files, 7 changes)

This is semantically aware — it only renames actual references to that symbol, not string matches in comments or unrelated variables that happen to share the name.

The Investigation Workflow

The most common LSP pattern is a three-step investigation: hover → findReferences → incomingCalls. Here's how it works in practice:

> I need to understand the calculate_total function before I change it

[Tool: LSP] hover  file: src/billing.py  line: 23  character: 5
  (function) calculate_total(items: List[LineItem], tax_rate: float = 0.0) -> Decimal
  """Calculate the total cost including optional tax."""

Now you know the signature. Next, see how widely it's used:

[Tool: LSP] findReferences  file: src/billing.py  line: 23  character: 5
  src/api/checkout.py    line 45
  src/api/invoices.py    line 78
  src/reports/monthly.py line 112
  tests/test_billing.py  line 15, 28, 42
  (6 references)

Six references — not trivial. Check the actual callers to understand the dependency chain:

[Tool: LSP] incomingCalls  file: src/billing.py  line: 23  character: 5
  process_checkout     src/api/checkout.py     line 40
  generate_invoice     src/api/invoices.py     line 72
  build_monthly_report src/reports/monthly.py  line 108

Three callers across three modules. Now you know exactly what will be affected before you touch anything.

LSP vs grep

This is the key decision. Use LSP when you need meaning, grep when you need text.

Task Best Tool Why
Find callers of a function LSP incomingCalls Semantic — skips comments and strings
Jump to a definition LSP goToDefinition Goes to the exact spot
Get type info or signature LSP hover grep can't infer types
Rename a symbol safely LSP rename Cross-file, semantically aware
Check for errors after edits LSP diagnostics Compiler-grade validation
Find a text pattern anywhere grep Faster, works on any file
Search comments or strings grep LSP ignores non-code content
Search non-code files grep LSP only covers source code
Bulk search across many files grep Text search is grep's strength

They also combine well. Use grep to find candidate files fast, then LSP to understand the semantic relationships within them.

Delegating Complex Navigation

For multi-step investigations that require chaining many LSP operations — tracing a call chain across five files, mapping out a class hierarchy, or building a dependency graph — delegate to the python-dev:code-intel agent rather than issuing operations one at a time. The agent handles the chaining, backtracking, and synthesis for you.

> Trace the full call chain from the /checkout API endpoint down to the database layer

[delegates to python-dev:code-intel agent]

Use direct LSP calls for quick, focused queries. Delegate when the investigation needs reasoning across many hops.

Tips

Run diagnostics after every edit. It's the fastest way to catch type errors, missing imports, and broken references before running tests. Make it a habit.

Start with hover. Before diving into definitions or references, hover tells you the type, signature, and docstring in one call. Often that's all you need.

Positions are 1-based. Line 1, character 1 is the top-left corner of the file. This matches what you see in read_file output with line numbers.

Fall back to grep when LSP can't help. Dynamic imports, metaprogramming, and string-based lookups are invisible to the language server. If LSP returns nothing, grep will still find the text.

Use rename instead of find-and-replace. rename understands scope — it won't accidentally rename a local variable that happens to share a name with the function you're targeting.

Next Steps

  • Learn about Search Tools for text-based search — the complement to LSP's semantic analysis
  • See Filesystem Tools for reading and editing the files LSP points you to
  • Explore Bash Tool for running tests after LSP-guided refactoring