Generic LSP Proxy for VS Code

A flexible Language Server Protocol (LSP) proxy extension for VS Code that dynamically forwards requests to any LSP server based on configuration files. No more installing separate extensions for each language!
🌟 Features
- 🔄 Dynamic LSP Loading - Automatically detect and load language servers based on file type
- 📝 Configuration-Driven - Simple JSON configuration for any LSP server
- 🚀 Multiple Transport Support - stdio, TCP, and WebSocket connections
- 📊 Status Bar Integration - Monitor active servers at a glance
- 🎯 Per-Workspace Configuration - Different settings for different projects
- 🛠️ Built-in Commands - Restart, reload, and manage servers easily
- 🛡️ Manual Server Management - Enable/disable servers as needed
📦 Installation
From VS Code Marketplace
- Open VS Code
- Press
Ctrl+P / Cmd+P
- Type
ext install generic-lsp-proxy
- Press Enter
From Source
# Clone the repository
git clone https://github.com/mjmorales/vscode-generic-lsp-proxy.git
cd vscode-generic-lsp-proxy
# Install dependencies
npm install
# Build and package
npm run compile
npx vsce package
# Install the VSIX
code --install-extension generic-lsp-proxy-*.vsix
🚀 Quick Start
1. Initialize Configuration
Option A: Use the Init Command (Recommended)
- Press
Ctrl+Shift+P / Cmd+Shift+P
- Run
LSP Proxy: Initialize LSP Configuration
- Choose
Custom Configuration:
- Enter language ID (e.g.,
python, rust, custom)
- Enter LSP command (e.g.,
pylsp, rust-analyzer)
- Enter file extensions (e.g.,
.py, .pyw)
- Select transport type (stdio, tcp, or websocket)
- Add optional arguments if needed
- Your
.vscode/lsp-proxy.json will be created automatically!
Option B: Create Manually
[
{
"languageId": "python",
"command": "pylsp",
"fileExtensions": [".py"]
}
]
2. Install Language Server
# Python
pip install python-lsp-server
# TypeScript
npm install -g typescript-language-server typescript
# Rust
rustup component add rust-analyzer
3. Open a File
Open any file matching your configured extensions. The LSP will start automatically!
📋 Configuration
Configuration File Locations
For every workspace folder the extension loads, in order:
- The path in the
genericLspProxy.configPath setting (default .vscode/lsp-proxy.json).
.lsp-proxy.json in the folder root — only as a fallback when the configPath file does not exist.
It then always also loads a global config file shared across all open folders:
lsp-proxy.json in the extension's globalStorage directory. This location is per VS Code profile; its filesystem path is shown in the extension's output channel on load. Use it for servers you want available in every workspace.
configPath scope and trust
genericLspProxy.configPath is read with VS Code's setting scopes, and its scope changes how the value is resolved:
- Workspace / folder scope (
.vscode/settings.json, *.code-workspace): the value is repo-controlled and treated as untrusted. It is resolved relative to the workspace folder and must stay inside it — an absolute path or a .. escape is rejected (logged as Invalid configPath … escapes workspace folder … skipping). This prevents an unverified repository from pointing the extension at an arbitrary file.
- User / profile scope (your User
settings.json): the value is set by you and is trusted. An absolute path is allowed and is loaded once, independent of any workspace folder — set it here to share one config across workspaces or to keep a per-profile config (e.g. a Nix-generated path). A relative user-scoped value is resolved per folder without the containment restriction.
// User settings.json — absolute path, shared across all workspaces / per profile
{
"genericLspProxy.configPath": "/path/to/generic-lsp-proxy.json"
}
Configuration Schema
interface LSPServerConfig {
// Required fields
languageId: string; // Unique identifier for the language
command: string; // Command to start the LSP server
fileExtensions: string[]; // File extensions to activate this server
// Optional fields
args?: string[]; // Command line arguments
filePatterns?: string[]; // Glob patterns for file matching
workspacePattern?: string; // Restrict to specific workspace folders
initializationOptions?: {}; // LSP initialization options
settings?: {}; // Language-specific settings
env?: {}; // Environment variables
transport?: 'stdio' | 'tcp' | 'websocket'; // Connection type
tcpPort?: number; // Port for TCP transport
websocketUrl?: string; // URL for WebSocket transport
}
Example Configurations
TypeScript/JavaScript
{
"languageId": "typescript",
"command": "typescript-language-server",
"args": ["--stdio"],
"fileExtensions": [".ts", ".tsx", ".js", ".jsx"],
"initializationOptions": {
"preferences": {
"includeCompletionsForModuleExports": true,
"includeInlayParameterNameHints": "all"
}
}
}
Python
{
"languageId": "python",
"command": "pylsp",
"fileExtensions": [".py", ".pyw"],
"settings": {
"pylsp": {
"plugins": {
"pycodestyle": {
"enabled": true,
"maxLineLength": 120
},
"pylint": {
"enabled": true
}
}
}
}
}
Rust
{
"languageId": "rust",
"command": "rust-analyzer",
"fileExtensions": [".rs"],
"initializationOptions": {
"cargo": {
"buildScripts": {
"enable": true
},
"features": "all"
},
"procMacro": {
"enable": true
}
}
}
Go
{
"languageId": "go",
"command": "gopls",
"fileExtensions": [".go"],
"initializationOptions": {
"usePlaceholders": true,
"completeUnimported": true,
"staticcheck": true
}
}
Remote Server (TCP)
{
"languageId": "gdscript",
"command": "nc",
"args": ["localhost", "6005"],
"transport": "tcp",
"tcpPort": 6005,
"fileExtensions": [".gd", ".tres", ".tscn"]
}
Multiple servers on one file type (e.g. language server + linter)
A single file can be driven by more than one language client. List one entry per
server that should run for the extension; every matching server is started and
VS Code merges their results (completions, hovers, diagnostics, definitions, …).
[
{
"languageId": "python",
"command": "pylsp",
"fileExtensions": [".py"]
},
{
"languageId": "python",
"command": "ruff",
"args": ["server"],
"fileExtensions": [".py"]
}
]
Entries are matched independently, so they may differ in command, transport,
args, or settings. Servers are tracked by a stable id (languageId::command),
which is why two entries sharing a languageId coexist as long as their commands
differ. Each server sees the whole file, so a server pointed at a host language
must tolerate it (e.g. tailwindcss-language-server on HTML is fine; a plain CSS
server on HTML is not).
Known limitation: Call Hierarchy (textDocument/prepareCallHierarchy) does
not merge across clients in VS Code — if one server returns an empty result
before a second returns data, the late result is dropped. This is an upstream
VS Code language-client behavior, not something the proxy can resolve.
⚙️ Extension Settings
Configure the extension behavior in VS Code settings (Ctrl+, / Cmd+,):
| Setting |
Type |
Default |
Description |
genericLspProxy.configPath |
string |
.vscode/lsp-proxy.json |
Path to configuration file. Workspace-scoped values must stay inside the folder; user/profile-scoped values may be absolute. See configPath scope and trust. |
genericLspProxy.enableDebugLogging |
boolean |
false |
Enable detailed logging |
📟 Commands
Access commands via Command Palette (Ctrl+Shift+P / Cmd+Shift+P):
- LSP Proxy: Initialize LSP Configuration - Create a new LSP configuration with guided setup
- LSP Proxy: Restart LSP Server - Restart the server for the current file
- LSP Proxy: Show Active LSP Servers - Display and manage running servers
- LSP Proxy: Reload LSP Configuration - Reload config and restart servers
- LSP Proxy: Show Disabled Servers - View and re-enable servers that failed to start
🔧 Development
This project uses Task for development workflows.
Prerequisites
# Install Task
brew install go-task/tap/go-task # macOS
# or see https://taskfile.dev/installation/
# Install dependencies
task install
Common Tasks
task # Show all available tasks
task dev # Start development mode
task test # Run all tests
task lint # Run linter
task build # Build the extension
task package # Create VSIX file
Project Structure
vscode-generic-lsp-proxy/
├── src/
│ ├── extension.ts # Extension entry point
│ ├── lspProxyManager.ts # LSP client lifecycle management
│ ├── configurationManager.ts # Configuration loading and validation
│ ├── logger.ts # Logging utilities
│ └── test/ # Test suites
├── examples/ # Example configurations
├── package.json # Extension manifest
├── Taskfile.yml # Task runner configuration
└── README.md # This file
Testing
# Run all tests
task test
# Run with coverage
task test:coverage
# Run specific test suite
npm test -- --grep "ConfigurationManager"
Code Quality
# Run all checks
task check
# Auto-fix issues
task fix
# Format code
task format
🐛 Troubleshooting
Server Not Starting
- Check Output Panel: View → Output → "Generic LSP Proxy"
- Verify Installation:
which <command> should return the path
- Test Manually: Run
<command> --version in terminal
- Check Configuration: Ensure JSON is valid and paths are correct
No IntelliSense/Completions
- File Extension: Verify file extension matches configuration
- Language Server Features: Not all servers support all features
- Initialization: Check
initializationOptions in config
- Debug Logging: Enable in settings to see LSP communication
- Reduce File Watchers: Limit
filePatterns scope
- Server Performance: Some servers are resource-intensive
- Check Output: Look for errors in the output panel
Common Error Messages
| Error |
Solution |
| "Command not found" |
Install the language server or check PATH |
| "Connection refused" |
For TCP, ensure server is running on specified port |
| "Invalid configuration" |
Check JSON syntax and required fields |
| "Server stopped" |
Check server logs and restart manually |
| "Invalid configPath … escapes workspace folder" |
A workspace-scoped configPath must stay inside the folder. To use an absolute path, set genericLspProxy.configPath in your User settings instead of workspace settings — see configPath scope and trust. |
🤝 Contributing
Contributions are welcome! Please follow these steps:
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature
- Make your changes and add tests
- Run checks:
task ready
- Commit:
git commit -m 'Add amazing feature'
- Push:
git push origin feature/amazing-feature
- Open a Pull Request
Development Guidelines
- Write tests for new features
- Follow existing code style
- Update documentation
- Add example configurations
- Run
task check before committing
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🙏 Acknowledgments
- Built with vscode-languageclient
- Inspired by the need for a unified LSP experience
- Thanks to all contributors and users
🔗 Links
Made with ❤️ for the VS Code community