LSP Integration Guide¶
Overview¶
The LSP (Language Server Protocol) integration provides enhanced semantic analysis capabilities by communicating with language servers like Pyright (Python) and typescript-language-server (TypeScript/JavaScript). This enables type-aware vulnerability detection and precise cross-file analysis.
Architecture¶
┌─────────────────────────────────────────────────────────────────┐
│ mcp-scan │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ LSP Manager ││
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌────────┐││
│ │ │ Python │ │ TypeScript │ │ JavaScript │ │ Go │││
│ │ │ Client │ │ Client │ │ Client │ │ Client │││
│ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └───┬────┘││
│ └─────────┼───────────────┼───────────────┼────────────┼──────┘│
└────────────┼───────────────┼───────────────┼────────────┼───────┘
│ │ │ │
┌────────▼────────┐ ┌────▼────┐ ┌────────▼────┐ ┌────▼────┐
│ Pyright │ │tsserver │ │ tsserver │ │ gopls │
│ (subprocess) │ │(subproc)│ │ (subprocess)│ │(subproc)│
└─────────────────┘ └─────────┘ └─────────────┘ └─────────┘
Components¶
LSP Client (internal/lsp/client.go)¶
The core client manages JSON-RPC 2.0 communication with language servers.
// Create a new LSP client
client, err := lsp.NewClient(ctx, "python", "/path/to/project")
if err != nil {
log.Fatal(err)
}
defer client.Close()
// Get type information at a position
typeInfo, err := client.GetTypeInfo(ctx, "file:///path/to/file.py", 10, 5)
if err != nil {
log.Printf("Failed to get type info: %v", err)
}
Supported Operations¶
| Operation | Method | Description |
|---|---|---|
| Hover | GetHover() |
Get hover information (types, docs) |
| Definition | GetDefinition() |
Go to definition location |
| References | GetReferences() |
Find all references to a symbol |
| Document Symbols | GetDocumentSymbols() |
List all symbols in a file |
| Call Hierarchy | PrepareCallHierarchy() |
Get call hierarchy for a function |
| Incoming Calls | GetIncomingCalls() |
Find callers of a function |
| Outgoing Calls | GetOutgoingCalls() |
Find functions called by a function |
LSP Manager (internal/lsp/manager.go)¶
The manager handles multiple language clients and provides a unified interface.
// Create manager for a project
manager := lsp.NewManager("/path/to/project")
// Get client for a specific language (starts server if needed)
client, err := manager.GetClient(ctx, "python")
if err != nil {
log.Printf("Python LSP not available: %v", err)
}
// Close all clients when done
defer manager.CloseAll()
LSP Types (internal/lsp/types.go)¶
Complete LSP protocol type definitions:
// Position in a document
type Position struct {
Line uint32 `json:"line"`
Character uint32 `json:"character"`
}
// Range in a document
type Range struct {
Start Position `json:"start"`
End Position `json:"end"`
}
// Location in a document
type Location struct {
URI string `json:"uri"`
Range Range `json:"range"`
}
// Hover result
type Hover struct {
Contents MarkupContent `json:"contents"`
Range *Range `json:"range,omitempty"`
}
// Call hierarchy item
type CallHierarchyItem struct {
Name string `json:"name"`
Kind SymbolKind `json:"kind"`
URI string `json:"uri"`
Range Range `json:"range"`
SelectionRange Range `json:"selectionRange"`
}
LSP Operations (internal/lsp/operations.go)¶
High-level operations built on the LSP protocol:
// TypeInfo extracted from hover
type TypeInfo struct {
Signature string // Full type signature
ReturnType string // Return type (for functions)
Parameters []string // Parameter types
Description string // Documentation
Kind string // function, variable, class, etc.
}
// Definition location
type Definition struct {
URI string
StartLine uint32
StartColumn uint32
EndLine uint32
EndColumn uint32
}
// Call graph node
type CallGraphNode struct {
Name string
URI string
Kind SymbolKind
Range Range
Callers []*CallGraphEdge
Callees []*CallGraphEdge
}
Language Server Installation¶
Supported Servers¶
| Language | Server | Command |
|---|---|---|
| Python | Pyright | pyright-langserver --stdio |
| TypeScript | typescript-language-server | typescript-language-server --stdio |
| JavaScript | typescript-language-server | typescript-language-server --stdio |
| Go | gopls | gopls serve |
Python (Pyright)¶
Installation:
# Install with npm (recommended)
npm install -g pyright
# Verify installation
pyright-langserver --version
Note: Pyright requires Node.js 14+.
Server command:
Capabilities: - Type inference - Import resolution - Call hierarchy - Find references - Go to definition
TypeScript/JavaScript (typescript-language-server)¶
Installation:
# Install both typescript and the language server
npm install -g typescript typescript-language-server
# Verify installation
typescript-language-server --version
Server command:
Capabilities: - Type inference - Module resolution - Call hierarchy - Find references - Go to definition
Go (gopls)¶
Installation:
# Install gopls
go install golang.org/x/tools/gopls@latest
# Add ~/go/bin to PATH if not already
export PATH=$PATH:~/go/bin
# Verify installation
gopls version
Server command:
Capabilities: - Type inference - Module resolution - Call hierarchy - Find references - Go to definition
Search Paths¶
mcp-scan searches for language servers in these locations:
- System
$PATH ~/go/bin(Go binaries)~/.local/bin/usr/local/bin/opt/homebrew/bin(macOS ARM)~/.npm-global/bin~/node_modules/.bin
CLI Usage¶
Enable LSP in Scan¶
# Scan with LSP enabled
mcp-scan scan --lsp ./my-project
# Scan with debug mode to see LSP activity
LSP_DEBUG=1 mcp-scan scan --lsp ./my-project
YAML Configuration¶
Progress Output¶
Normal mode:
[*] Scanning ./my-project...
[*] LSP: enabled
[*] Discovered 5 files
[*] Running pattern analysis...
[+] Scan completed in 2.3s
[+] Found 3 findings (2 high, 1 medium)
Debug mode (LSP_DEBUG=1):
[LSP] Found 5 symbols, 4 functions in server.py
[LSP] Analyzing tool get_company_data at server.py:31:4
[LSP] type: (function) def get_company_data(data_type: str) -> str
[LSP] call graph: get_company_data -> 1 callees
LSP Detector Integration¶
The LSPDetector uses LSP capabilities for enhanced vulnerability detection.
Configuration¶
cfg := pattern.LSPDetectorConfig{
RootPath: "/path/to/project",
Languages: []string{"python", "typescript", "javascript"},
}
detector, err := pattern.NewLSPDetector(cfg)
if err != nil {
log.Fatal(err)
}
defer detector.Close()
Detection Flow¶
1. Tool Handler Identified
│
▼
2. Query LSP for Type Info
│
▼
3. Check for Dangerous Types (Any, object, unknown)
│
▼
4. Build Call Graph from Handler
│
▼
5. Traverse Graph for Dangerous Sinks
│
▼
6. Generate Findings with Type Context
Detection Rules¶
LSP-TYPE-001: Dangerous Type Pattern¶
Detects when a tool parameter has a dangerous type like Any, object, or unknown.
LSP-SINK-001: Tool Input to Dangerous Sink¶
Detects when a tool handler can reach a dangerous sink via call graph analysis.
@tool
def execute_command(cmd: str):
# LSP builds call graph and finds path to os.system
helper(cmd)
def helper(command):
run_shell(command)
def run_shell(cmd):
os.system(cmd) # LSP-SINK-001: Dangerous sink reached
Call Graph Building¶
Algorithm¶
func (c *Client) BuildCallGraph(ctx context.Context, uri string,
line, character uint32, maxDepth int) (*CallGraphNode, error) {
// 1. Prepare call hierarchy at position
items, err := c.PrepareCallHierarchy(ctx, uri, line, character)
// 2. Create root node
root := &CallGraphNode{
Name: items[0].Name,
URI: items[0].URI,
Kind: items[0].Kind,
Range: items[0].Range,
}
// 3. Recursively build graph
visited := make(map[string]bool)
c.buildCallGraphRecursive(ctx, items[0], root, visited, 0, maxDepth)
return root, nil
}
Depth Limiting¶
The maxDepth parameter controls how deep the call graph exploration goes:
| Depth | Use Case |
|---|---|
| 1-2 | Quick checks, direct calls only |
| 3-5 | Standard analysis, most vulnerabilities |
| 5-10 | Deep analysis, complex call chains |
| 10+ | Exhaustive analysis, performance impact |
Type Information Extraction¶
Hover Parsing¶
The LSP hover response contains type information in markdown format:
Process user input data.
Parsed into:
```go
TypeInfo{
Signature: "def process(data: str) -> None",
ReturnType: "None",
Parameters: []string{"data: str"},
Kind: "function",
Description: "Process user input data.",
}
Dangerous Type Detection¶
Types considered dangerous for security analysis:
| Type | Language | Risk |
|---|---|---|
Any |
Python | No type checking |
object |
Python/TS | Generic, no constraints |
unknown |
TypeScript | Unsafe type |
any |
TypeScript | No type checking |
void* |
C/C++ | Raw pointer |
Performance Considerations¶
Server Startup¶
LSP servers have startup overhead:
| Server | Cold Start | Warm |
|---|---|---|
| Pyright | 2-5s | <100ms |
| tsserver | 1-3s | <100ms |
Optimization: Keep servers running between analyses.
Memory Usage¶
| Server | Base Memory | Per 1000 Files |
|---|---|---|
| Pyright | ~150MB | +50MB |
| tsserver | ~200MB | +80MB |
Caching¶
The manager caches active clients:
type Manager struct {
rootPath string
clients map[string]*Client // Cached by language
mu sync.Mutex
}
Error Handling¶
Server Not Available¶
client, err := manager.GetClient(ctx, "python")
if err != nil {
// Fallback to non-LSP analysis
log.Printf("LSP not available, using basic analysis: %v", err)
return basicAnalysis(file)
}
Request Timeout¶
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
result, err := client.GetHover(ctx, uri, line, col)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
log.Printf("LSP request timed out")
}
}
Server Crash Recovery¶
// Client automatically restarts server on failure
func (c *Client) Call(ctx context.Context, method string,
params, result interface{}) error {
err := c.sendRequest(ctx, method, params, result)
if err != nil && isServerDead(err) {
// Attempt restart
if restartErr := c.restart(ctx); restartErr != nil {
return fmt.Errorf("server restart failed: %w", restartErr)
}
// Retry request
return c.sendRequest(ctx, method, params, result)
}
return err
}
Integration Examples¶
With Pattern Engine¶
func NewEnhancedEngine(rootPath string) (*pattern.Engine, error) {
engine := pattern.New()
// Add LSP detector
lspDetector, err := pattern.NewLSPDetector(pattern.LSPDetectorConfig{
RootPath: rootPath,
Languages: []string{"python", "typescript"},
})
if err == nil && lspDetector.IsEnabled() {
engine.AddRule(&pattern.Rule{
ID: "LSP-ENHANCED",
Detector: lspDetector,
})
}
return engine, nil
}
With Taint Engine¶
func EnhancedTaintAnalysis(file *ast.File, lspClient *lsp.Client) {
engine := taint.NewEngine()
for _, source := range engine.GetSources(file) {
// Get precise type from LSP
typeInfo, _ := lspClient.GetTypeInfo(ctx,
"file://"+file.Path,
uint32(source.Location.StartLine),
uint32(source.Location.StartCol))
if typeInfo != nil {
// Use type info to improve taint precision
source.TypeInfo = typeInfo
}
}
}
Troubleshooting¶
Common Issues¶
Issue: Server fails to start
Solution: Install the language server globally:Issue: No type information returned
Solution: Ensure the file is part of a valid project with proper configuration (pyproject.toml, tsconfig.json).Issue: Slow analysis
Solution:
1. Reduce maxDepth in call graph building
2. Limit number of files analyzed
3. Use warm server (don't restart between files)
Debug Logging¶
Enable LSP debug logging:
API Reference¶
Client Methods¶
| Method | Parameters | Returns | Description |
|---|---|---|---|
NewClient |
ctx, language, rootPath | *Client, error | Create new client |
Close |
- | error | Shutdown server |
GetHover |
ctx, uri, line, char | *Hover, error | Get hover info |
GetTypeInfo |
ctx, uri, line, char | *TypeInfo, error | Get structured type info |
GetDefinition |
ctx, uri, line, char | []Definition, error | Go to definition |
GetReferences |
ctx, uri, line, char, includeDecl | []Definition, error | Find references |
GetDocumentSymbols |
ctx, uri | []Symbol, error | List symbols |
PrepareCallHierarchy |
ctx, uri, line, char | []CallHierarchyItem, error | Prepare call hierarchy |
GetIncomingCalls |
ctx, item | []CallHierarchyIncomingCall, error | Get callers |
GetOutgoingCalls |
ctx, item | []CallHierarchyOutgoingCall, error | Get callees |
BuildCallGraph |
ctx, uri, line, char, maxDepth | *CallGraphNode, error | Build full call graph |
Manager Methods¶
| Method | Parameters | Returns | Description |
|---|---|---|---|
NewManager |
rootPath | *Manager | Create manager |
GetClient |
ctx, language | *Client, error | Get or create client |
CloseAll |
- | error | Close all clients |