A Model Context Protocol (MCP) server that wraps Language Server Protocol (LSP) servers, making language server capabilities available to LLMs through MCP.
This project bridges two important protocols:
- LSP (Language Server Protocol): Provides semantic understanding of code (definitions, references, diagnostics, etc.)
- MCP (Model Context Protocol): Makes these capabilities accessible to LLMs
By wrapping LSP servers with MCP, LLMs can gain deep semantic understanding of codebases, enabling them to navigate, analyze, and refactor code more effectively.
src/
├── index.ts # Main entry point and MCP server setup
├── logging/ # Logging infrastructure
│ └── logger.ts # Component-based logging system
├── protocol/ # LSP protocol types
│ ├── types.ts # Type definitions and wrappers
│ └── uri.ts # URI utilities
├── lsp/ # LSP client implementation
│ ├── client.ts # LSP client and process management
│ ├── transport.ts # JSON-RPC message transport
│ └── methods.ts # LSP method wrappers
├── watcher/ # File system watching
│ ├── watcher.ts # Workspace file watcher
│ └── gitignore.ts # Gitignore pattern matching
└── tools/ # MCP tool implementations
├── utilities.ts # Shared utility functions
├── definition.ts # Get symbol definitions
├── references.ts # Find symbol references
├── hover.ts # Get hover information
├── diagnostics.ts # Get diagnostics (errors/warnings)
├── edit.ts # Apply text edits
└── rename.ts # Rename symbols
Purpose: Provides structured, component-based logging with configurable levels.
Key Features:
- Component-based filtering (Core, LSP, Wire, LSP Process, Watcher, Tools)
- Configurable log levels (DEBUG, INFO, WARN, ERROR, FATAL)
- Environment variable configuration (
LOG_LEVEL,LOG_COMPONENT_LEVELS,LOG_FILE)
Example:
const logger = createLogger(Component.LSP);
logger.debug('Processing request: %s', requestId);
logger.error('Failed to initialize: %s', err);Purpose: Type definitions and utilities for LSP protocol.
Components:
types.ts: Re-exports VSCode LSP types and provides wrapper interfacesuri.ts: URI conversion utilities (pathToUri,uriToPath)
Key Abstractions:
ISymbol: Unified interface forSymbolInformationandWorkspaceSymbolSymbolKindNames: Human-readable names for symbol kindsFileChangeType,WatchKind: File watching enums
Purpose: Manages communication with LSP server processes.
Components:
- Implements LSP message framing (Content-Length headers)
- Handles message serialization/deserialization
- Supports both requests and notifications
Message Flow:
Client Request → JSON-RPC → LSP Server
← Response ←
- Spawns and manages LSP server process
- Handles three types of communication:
- Client → Server requests: Tool calls (definition, hover, etc.)
- Server → Client requests: Capability registration, workspace edits
- Server → Client notifications: Diagnostics, messages
Key Methods:
initialize(): Initialize LSP server with workspace configurationopenFile(): Open files for analysiscall(): Make LSP requests (with response)notify(): Send LSP notifications (no response)registerServerRequestHandler(): Handle server-initiated requestsregisterNotificationHandler(): Handle server notifications
Process Management:
- Automatic process cleanup on exit
- Graceful shutdown with timeout and fallback kill
- stderr capture for logging
Provides typed wrappers for common LSP methods:
symbol(): Workspace symbol searchreferences(): Find all referenceshover(): Get hover informationrename(): Rename symboldefinition(): Go to definition
Purpose: Monitor workspace files and sync changes with LSP server.
Components:
- Loads and parses
.gitignorefiles - Determines if files/directories should be ignored
- Uses
ignorelibrary for pattern matching
Responsibilities:
- File System Monitoring: Uses
chokidarto watch for file changes - Pattern Matching: Supports LSP glob patterns (
**/*.ts,*.{js,ts}) - Smart Filtering: Excludes
node_modules,.git, build artifacts - Debouncing: Reduces notification spam
- File Opening: Automatically opens files matching registered patterns
Event Flow:
File Change → Filter → Debounce → Notify LSP Server
→ Open/Close Files
Configuration:
excludedDirs: Directories to skip (e.g.,node_modules)excludedFileExtensions: File types to ignore (e.g.,.pyc)maxFileSize: Skip large binary filesdebounceTime: Delay before sending notifications
Purpose: Implement MCP tools that expose LSP capabilities.
addLineNumbers(): Format code with line numbersgetFullDefinition(): Expand definition to include comments/bodygetLineRangesToDisplay(): Calculate context rangesformatLinesWithRanges(): Format output with line ranges
Each tool follows a similar pattern:
- Parse and validate inputs
- Open necessary files in LSP
- Call appropriate LSP methods
- Format results for display
- Return formatted text
definition.ts - Symbol Definition Lookup
readDefinition(client, 'MyClass.myMethod')
→ Searches workspace symbols
→ Opens files containing matches
→ Expands definition range (includes comments)
→ Returns formatted code with location inforeferences.ts - Find All References
findReferences(client, 'myFunction')
→ Finds symbol via workspace/symbol
→ Calls textDocument/references
→ Groups by file
→ Shows context around each referencehover.ts - Get Hover Information
getHoverInfo(client, 'file.ts', 10, 5)
→ Calls textDocument/hover
→ Formats hover content (markdown/plaintext)
→ Returns type info and documentationdiagnostics.ts - Get Diagnostics
getDiagnosticsForFile(client, 'file.ts')
→ Opens file
→ Retrieves cached diagnostics
→ Groups by severity
→ Shows context around each diagnosticedit.ts - Apply Text Edits
applyTextEdits(client, 'file.ts', [
{ startLine: 10, endLine: 12, newText: 'new code' }
])
→ Opens file
→ Converts to LSP TextEdit format
→ Applies edits from bottom to top
→ Writes back to filesystemrename.ts - Rename Symbol
renameSymbol(client, 'file.ts', 10, 5, 'newName')
→ Calls textDocument/rename
→ Receives WorkspaceEdit
→ Applies changes across all files
→ Returns summary of changesPurpose: Orchestrates all components and exposes MCP tools.
Lifecycle:
- Parse Configuration: Command-line arguments (workspace, LSP command)
- Initialize LSP: Start LSP server process, send initialize request
- Start Watcher: Monitor workspace files
- Register Tools: Define available MCP tools
- Start MCP Server: Listen on stdio for MCP requests
- Handle Requests: Route tool calls to appropriate handlers
- Graceful Shutdown: Close files, shutdown LSP, cleanup
Configuration Options:
mcp-language-server \
--workspace /path/to/project \
--lsp typescript-language-server \
-- --stdio--workspace: Project directory--lsp: LSP server command--: Arguments after this are passed to LSP server
MCP Client (LLM)
↓ (stdio)
MCP Server (index.ts)
↓
Tool Handler (tools/definition.ts)
↓
LSP Client (lsp/client.ts)
↓ (JSON-RPC over stdio)
LSP Server Process (e.g., typescript-language-server)
↓
File System / Code Analysis
↑
Results flow back up the chain
File System Change
↓
chokidar (watcher.ts)
↓
Filter & Debounce
↓
LSP Client
↓ workspace/didChangeWatchedFiles
LSP Server
↓
Diagnostics Published
↓
Cached in LSP Client
↓
Available to Tools
- Clear separation between transport, protocol, and tool layers
- Each layer has well-defined responsibilities
- Abstractions allow for testing and modification
- LSP uses bidirectional JSON-RPC
- Server can send requests/notifications to client
- Handlers registered for different message types
- Files opened/closed explicitly
- Process cleanup on shutdown
- Timeout-based fallbacks for cleanup
- File change events debounced to reduce noise
- Diagnostics cached in client
- Open file state tracked
- Errors propagated with context
- Logging at appropriate levels
- Graceful degradation where possible
LOG_LEVEL: Set global log level (DEBUG, INFO, WARN, ERROR, FATAL)LOG_COMPONENT_LEVELS: Set per-component levels (e.g.,lsp:DEBUG,tools:INFO)LOG_FILE: Write logs to file in addition to stderrLSP_CONTEXT_LINES: Lines of context for references (default: 5)
export LOG_LEVEL=DEBUG
mcp-language-server --workspace . --lsp goplsFocus on individual components:
- Logging configuration
- URI conversion
- Message framing/parsing
- Pattern matching
- Text edit application
Test end-to-end flows:
- LSP initialization
- File opening/closing
- Tool execution
- Workspace watching
The codebase is designed for extensibility:
- New Tools: Add files to
tools/and register inindex.ts - Custom Filters: Modify watcher configuration
- LSP Methods: Add wrappers in
lsp/methods.ts - Custom Logging: Add new components or log sinks
This TypeScript implementation maintains architectural parity with the Go version while leveraging TypeScript/Node.js ecosystems:
| Aspect | Go Version | TypeScript Version |
|---|---|---|
| LSP Types | Generated from gopls | VSCode LSP types |
| File Watching | fsnotify | chokidar |
| Gitignore | go-gitignore | ignore |
| Concurrency | Goroutines & channels | Async/await & Promises |
| Process Management | os/exec | child_process |
| MCP SDK | mcp-go | @modelcontextprotocol/sdk |
@modelcontextprotocol/sdk: MCP protocol implementationvscode-languageserver-protocol: LSP type definitionschokidar: File system watcherignore: Gitignore pattern matching
# Install dependencies
npm install
# Build
npm run build
# Run
node dist/index.js --workspace /path/to/project --lsp typescript-language-server -- --stdioBSD-3-Clause (same as original Go implementation)