A bootstrap kit that wires LSP (Language Server Protocol) tooling into any repo via a custom MCP bridge daemon. Drop in one script, run it, and get type-checking, go-to-definition, hover, call hierarchy, rename, and diagnostics available to Claude Code and opencode — all with machine-specific paths gitignored.
Claude Code / Codex / opencode (default: HTTP bridge)
│ MCP HTTP (JSON-RPC)
▼
lsp-mcp-bridge (localhost:7890) ← built here, started by start-lsp.sh
│ LSP (JSON-RPC over stdio)
▼
pyright-langserver / typescript-language-server / gopls / ...
│ reads
▼
workspace files on disk
Alternative: per-language stdio (--stdio flag)
Claude Code / Codex
│ stdio
▼
mcp-language-server (one per language)
│ LSP (JSON-RPC over stdio)
▼
pyright-langserver / typescript-language-server / gopls / ...
Default: all agents connect through a single HTTP bridge daemon. The --stdio flag switches to per-language stdio servers (requires mcp-language-server).
generate-env-lsp.sh resolves binary paths on this machine and writes env.lsp. start-lsp.sh reads env.lsp and starts the bridge daemon.
# One-command bootstrap for any repo (recommended):
~/workspace/lsp-bootstrap/bootstrap-repo.sh ~/workspace/my-project --all
# Detects languages, writes config, builds bridge, starts daemon, checks health
# Or step-by-step:
cd ~/workspace/my-project/
~/workspace/lsp-bootstrap/generate-env-lsp.sh --all
~/workspace/lsp-bootstrap/lsp-mcp-bridge/just install
make -f Makefile.lsp lsp-start
make -f Makefile.lsp lsp-healthgenerate-env-lsp.sh must be run from within the target repo (it uses git rev-parse --show-toplevel to find the root). bootstrap-repo.sh handles this automatically.
# Required — Python and TypeScript LSP
npm install -g pyright typescript-language-server typescript
# Optional — stdio wrapper (only needed with --stdio flag)
# HTTP bridge mode (default) does not require this
go install github.com/isaacs/mcp-language-server@latest
# Optional — install only what your project needs
rustup component add rust-analyzer # Rust
go install golang.org/x/tools/gopls@latest # Go
npm install -g bash-language-server # Shell
npm install -g yaml-language-server # YAML
cargo install taplo-cli # TOML
npm install -g vscode-langservers-extracted # JSON, HTML, CSS
# Kotlin — download from https://github.com/fwcd/kotlin-language-server/releases
# Scala — cs install metals
# Codex — npm install -g @openai/codexJava requires a generated wrapper script — see Java.
cd lsp-mcp-bridge
just # list available commands
just build # compile → releases/lsp-mcp-bridge
just install # build + copy to ~/.local/bin (override: INSTALL_DIR=/your/path just install)
just test
just test-verbose./start-lsp.sh # checks prerequisites, starts daemon, writes /tmp/lsp-bridge.pid
./stop-lsp.sh # sends SIGTERM and removes PID filestart-lsp.sh is idempotent — running it twice when already started is a no-op.
curl http://localhost:7890/health{
"status": "ok",
"uptime": "2m30s",
"version": "0.1.0",
"slots": {
"python": {"configured": true, "running": true, "dead": false},
"typescript": {"configured": true, "running": false, "dead": false}
}
}Slots are running: false until the first tool call triggers lazy LSP startup. dead: true means the slot exceeded its failure threshold and will not be retried.
Note:
GET /mcphangs — that endpoint only handles MCP JSON-RPC POST requests. Always use/healthto check liveness.
Once .mcp.json is loaded in Claude Code, the lsp server exposes:
| Tool | Description |
|---|---|
hover |
Type signature and docs at a file/line/col |
definition |
Jump-to-definition location |
references |
All locations where a symbol is referenced |
diagnostics |
Errors and warnings for a file |
rename |
Rename a symbol — returns a unified diff, nothing written to disk |
call_hierarchy_in |
All callers of a function |
call_hierarchy_out |
All callees of a function |
signature_help |
Parameter names and types for a call site |
All tools take filePath (absolute path), line (1-based), and column (1-based) where applicable.
One-command bootstrap: detects languages, writes config, builds/installs the bridge, starts the daemon, and verifies health — all targeting any repo.
~/workspace/lsp-bootstrap/bootstrap-repo.sh ~/workspace/my-project --allBy default wires all agents (Claude Code, Codex, opencode) through the HTTP bridge — a single daemon multiplexing all language servers. Pass --stdio to use per-language stdio servers instead (requires mcp-language-server).
Flags: --codex, --opencode, --all, --stdio. Always passes --force internally.
Run once per machine (re-run after switching Python envs or upgrading language servers).
./generate-env-lsp.sh # detect languages, write env.lsp + scripts + Makefile.lsp + .mcp.json
./generate-env-lsp.sh --force # overwrite all previously generated files
./generate-env-lsp.sh --codex # also wire Codex via ~/.codex/config.toml
./generate-env-lsp.sh --opencode # also write .opencode/opencode.json for opencode
./generate-env-lsp.sh --all # wire Claude Code + Codex + opencode
./generate-env-lsp.sh --stdio # use per-language stdio servers (default: HTTP bridge)Writes:
| File | Committed? | Purpose |
|---|---|---|
env.lsp |
No | Machine-specific binary paths |
env.custom |
No | Optional local overrides (sourced before env.lsp) |
.mcp.json |
No | Wires Claude Code to HTTP bridge (or per-language stdio with --stdio) |
~/.codex/config.toml |
N/A | Wires per-language MCP servers into Codex (with --codex) |
.opencode/opencode.json |
No | Wires bridge into opencode over HTTP (with --opencode) |
start-lsp.sh |
Yes | Starts the bridge daemon |
stop-lsp.sh |
Yes | Stops the bridge daemon |
check-types.sh |
Yes | Runs pyright over the project |
Makefile.lsp |
Yes | Make targets (lsp-init, lsp-start, lsp-stop, lsp-restart, lsp-check, lsp-health, lsp-status) |
pyrightconfig.json |
Yes | Python type-check config (scaffolded if missing) |
jsconfig.json |
Yes | JS/TS config (scaffolded if missing) |
Language auto-detection:
| Language | Detected by |
|---|---|
| Python | setup.py, pyproject.toml, requirements.txt, or common dirs |
| JavaScript/TypeScript | package.json, jsconfig.json |
| Rust | Cargo.toml |
| Go | go.mod, or *.go files (up to 3 levels deep) |
| Kotlin | build.gradle.kts, settings.gradle.kts, *.kt |
| Scala | build.sbt, *.scala |
| Shell | *.sh, Makefile, Dockerfile |
| YAML | *.yml, *.yaml |
| TOML | *.toml |
| JSON | *.json (excluding node_modules) |
| HTML | *.html (excluding node_modules) |
| CSS/SCSS/Less | *.css, *.scss, *.less |
Each workspace gets its own bridge instance. generate-env-lsp.sh derives a stable port (in the range 7890–7989) and a workspace-scoped PID file from the workspace path, so two terminals running their own start-lsp.sh will not conflict:
workspace/base1 → port 7927 /tmp/lsp-bridge-base1.pid
workspace/base2 → port 7928 /tmp/lsp-bridge-base2.pid
Each workspace's .mcp.json points to its own port, so Claude Code sessions pick up the right bridge automatically.
env.lsp is regenerated — never edit it directly. Put local overrides in env.custom:
# env.custom — gitignored, never committed
LSP_PYTHON=/opt/homebrew/bin/python3.12
LSP_PORT=7891 # manual override if the auto-assigned port conflictsDefault (HTTP bridge) — single entry, all languages through the bridge:
[mcp_servers.lsp]
url = "http://127.0.0.1:7890/mcp"Stdio (with --stdio) — per-language stdio entries via mcp-language-server wrappers:
[mcp_servers.language-server-python]
command = "/path/to/mcp-language-server"
transport = "stdio"
args = ["-workspace", "/path/to/repo", "-lsp", "/path/to/pyright-langserver", "--", "--stdio"]
env = {"LOG_LEVEL" = "INFO"}Each stdio entry requires a separate mcp-language-server process. The HTTP bridge is one process handling all languages.
If Codex is not installed, the generator warns but continues — Claude Code and opencode are still wired.
Prerequisites for Codex:
npm install -g @openai/codexWith --opencode, entries are written to .opencode/opencode.json (per-repo config).
Default (HTTP bridge) — single entry, all languages through the bridge:
{
"mcp": {
"lsp": {
"type": "http",
"url": "http://127.0.0.1:7890/mcp"
}
}
}Stdio (with --stdio) — per-language stdio entries via mcp-language-server wrappers:
{
"mcp": {
"language-server-go": {
"type": "local",
"command": ["/path/to/mcp-language-server", "-workspace", "/path/to/repo", "-lsp", "/path/to/gopls"]
}
}
}The bridge must be running (make -f Makefile.lsp lsp-start) for HTTP mode. Stdio mode launches servers on-demand.
Note: .opencode/opencode.json is machine-specific and automatically added to .gitignore.
Java uses jdtls, which requires JVM flags, a launcher JAR, and a per-workspace data directory. A separate script generates a wrapper that lsp-mcp-bridge can launch like any other binary.
# 1. Install JDK 17+
# macOS: brew install temurin
# Linux: apt install openjdk-21-jdk
# 2. Install jdtls
# macOS: brew install jdtls
# Linux: download from https://www.eclipse.org/jdtls/
# 3. Generate the wrapper
./generate-java-lsp.sh
# Override jdtls location if non-standard:
JDTLS_HOME_OVERRIDE=/path/to/jdtls ./generate-java-lsp.shGenerates jdtls-wrapper.sh (gitignored) and a .jdtls-data/ workspace index directory. The wrapper is injected into .mcp.json alongside other language servers.
| Command | What it does |
|---|---|
bootstrap-repo.sh <repo> |
Full bootstrap for a target repo (recommended) |
./start-lsp.sh |
Start the bridge daemon |
./stop-lsp.sh |
Stop the bridge daemon |
curl localhost:7890/health |
Check bridge liveness and slot status |
./check-types.sh |
Run pyright over the whole project |
./check-types.sh path/to/file.py |
Check a single file |
make -f Makefile.lsp lsp-start |
Start bridge via make |
make -f Makefile.lsp lsp-stop |
Stop bridge via make |
make -f Makefile.lsp lsp-restart |
Stop then start bridge via make |
make -f Makefile.lsp lsp-check ARGS='path/to/file.py' |
Type-check one file via make |
make -f Makefile.lsp lsp-health |
Health check using the project's generated port |
make -f Makefile.lsp lsp-status |
Print the bridge health endpoint URL |
./generate-env-lsp.sh |
Refresh env.lsp and .mcp.json after path changes |
just install |
Rebuild and reinstall the bridge binary |
The generator appends to .gitignore automatically:
env.lsp
env.custom
.mcp.json
.opencode/opencode.json
Safe to commit: start-lsp.sh, stop-lsp.sh, check-types.sh, pyrightconfig.json, jsconfig.json. Never commit env.lsp or .mcp.json — they contain machine-specific absolute paths.
If your Go project isn't detected, check:
go.modexists at repo root — the primary detection signal*.gofiles exist within 3 levels — fallback detection (same as Kotlin/Scala)- If neither applies, initialize a module:
go mod init <module-name>
Then re-run: ./generate-env-lsp.sh --force
- Check that
lsp-mcp-bridgeis on your PATH:which lsp-mcp-bridge - Check the bridge log:
cat logs/lsp-bridge.log - Check for stale PID file:
rm /tmp/lsp-bridge-*.pidand retry
If a language is detected but the server isn't wired, the generator printed a warn line during binary resolution. Install the missing binary (e.g. go install golang.org/x/tools/gopls@latest) and re-run with --force.