LSP Assist
Precise code navigation via Language Server Protocol.
Modes
| Mode | How | When to use |
|---|
| Daemon (recommended) | INLINECODE0 then HTTP queries | Multiple queries in a session (start once, query many) |
| One-shot |
goto/refs/hover/diag/symbols directly | Single quick query, or scripting |
Requirements
- - Runtime: Python 3.10+ (standard library only)
- OS: Linux, macOS
- Language servers (install separately):
- TypeScript:
npm i -g typescript-language-server typescript
- Python:
pip install pyright
- TypeScript:
tsconfig.json in project root
- Python:
pyproject.toml or INLINECODE6
Security
Binding
Daemon binds to
127.0.0.1 only — no external/remote access:
CODEBLOCK0
File Access Control
All file reads go through
_open_file(), which enforces project-root containment:
def _open_file(self, filepath: str):
path = Path(filepath).resolve() # resolve symlinks, .., etc.
if str(path) in self._opened_files:
return # skip duplicate opens
common = os.path.commonpath([str(path), self.root_path])
if common != self.root_path: # must be direct ancestor of root
raise ValueError(f"File outside project root: {path}")
# Only then: read file content and send didOpen to language server
- -
Path.resolve() canonicalizes the path (resolves symlinks, .., ~) - INLINECODE12 rejects sibling dirs (e.g.
/root/openclaw-backup/ when root is /root/openclaw) - Tested against: symlink escape,
.. traversal, trailing-slash differences, sibling-prefix collision
Auth Token (optional)
When
--token <secret> is passed, every HTTP request must include
Authorization: Bearer <secret>:
def _check_auth(self) -> bool:
expected = getattr(self.server, "auth_token", None)
if expected is None:
return True # no token required
header = self.headers.get("Authorization", "")
if header == f"Bearer {expected}":
return True
self._respond({"error": "unauthorized"}, 401)
return False
- - Checked in both
do_GET and do_POST before any handler logic - Without
--token, daemon is open on localhost (intended for local single-user use)
Shutdown Handling
elif self.path == "/shutdown":
self._respond({"status": "shutting down"})
threading.Thread(target=lambda: (time.sleep(0.5), os._exit(0)), daemon=True).start()
Responds first, then exits after 0.5s delay to ensure the HTTP response is flushed.
Process Isolation
- - One-shot mode: fresh server per query, auto-terminates (15s timeout)
- Daemon mode: persistent server, but
is_alive() check returns 503 if the language server crashes - Language server communication is local stdio only
Quick Reference
Daemon Mode (recommended for multi-query)
CODEBLOCK4
One-shot Mode
| Action | Command |
|---|
| Go to definition | INLINECODE22 |
| Find references |
python3 scripts/lsp_client.py --lang ts --root <dir> refs --file <f> --line <n> --col <n> |
| Hover type info |
python3 scripts/lsp_client.py --lang ts --root <dir> hover --file <f> --line <n> --col <n> |
| Diagnostics |
python3 scripts/lsp_client.py --lang ts --root <dir> diag --file <f> |
| Symbol search |
python3 scripts/lsp_client.py --lang ts --root <dir> symbols [--query <text>] |
Language shorthand: --lang typescript or --lang ts or --lang python.
Output Format
All commands output JSON for structured consumption:
CODEBLOCK5
When to Use vs Grep
| Scenario | Use |
|---|
| "Where is this function defined?" | LSP goto |
| "Who calls this function?" |
LSP refs |
| "What type is this variable?" | LSP hover |
| "Any compile errors?" | LSP diag |
| "Find all classes matching X" | LSP symbols |
| "Search for text pattern 'TODO'" | grep |
| "Find all .json files" | glob |
| "Search across non-code files" | grep |
Supported Languages
| Language | Server | Install |
|---|
| TypeScript/JavaScript | typescript-language-server | INLINECODE30 |
| Python |
pyright |
pip install pyright |
Adding a language: edit SERVERS dict in lsp_client.py — add the server command and file extensions.
LSP 辅助
通过语言服务器协议实现精确的代码导航。
模式
| 模式 | 操作方式 | 使用场景 |
|---|
| 守护进程(推荐) | daemon --port 9876 后发送 HTTP 查询 | 会话中多次查询(启动一次,查询多次) |
| 单次模式 |
直接使用 goto/refs/hover/diag/symbols | 单次快速查询或脚本调用 |
要求
- - 运行环境:Python 3.10+(仅使用标准库)
- 操作系统:Linux、macOS
- 语言服务器(需单独安装):
- TypeScript:npm i -g typescript-language-server typescript
- Python:pip install pyright
- TypeScript:项目根目录下的 tsconfig.json
- Python:pyproject.toml 或 pyrightconfig.json
安全性
绑定地址
守护进程仅绑定到 127.0.0.1 —— 无外部/远程访问:
python
server = HTTPServer((127.0.0.1, port), DaemonHandler)
文件访问控制
所有文件读取均通过
openfile() 进行,该函数强制项目根目录包含检查:
python
def
openfile(self, filepath: str):
path = Path(filepath).resolve() # 解析符号链接、.. 等
if str(path) in self.
openedfiles:
return # 跳过重复打开
common = os.path.commonpath([str(path), self.root_path])
if common != self.root_path: # 必须是根目录的直接祖先
raise ValueError(f文件位于项目根目录之外:{path})
# 仅在此之后:读取文件内容并向语言服务器发送 didOpen
- - Path.resolve() 规范化路径(解析符号链接、..、~)
- os.path.commonpath 拒绝同级目录(例如根目录为 /root/openclaw 时拒绝 /root/openclaw-backup/)
- 已测试:符号链接逃逸、.. 遍历、尾部斜杠差异、同级前缀冲突
认证令牌(可选)
当传递 --token
时,每个 HTTP 请求必须包含 Authorization: Bearer :
python
def checkauth(self) -> bool:
expected = getattr(self.server, auth_token, None)
if expected is None:
return True # 无需令牌
header = self.headers.get(Authorization, )
if header == fBearer {expected}:
return True
self._respond({error: unauthorized}, 401)
return False
- - 在 doGET 和 doPOST 中均会在处理逻辑前进行检查
- 未使用 --token 时,守护进程在本地主机上开放(适用于本地单用户使用)
关闭处理
python
elif self.path == /shutdown:
self._respond({status: shutting down})
threading.Thread(target=lambda: (time.sleep(0.5), os._exit(0)), daemon=True).start()
先响应,然后延迟 0.5 秒后退出,以确保 HTTP 响应已刷新。
进程隔离
- - 单次模式:每次查询新建服务器,自动终止(15 秒超时)
- 守护进程模式:持久化服务器,但如果语言服务器崩溃,is_alive() 检查返回 503
- 语言服务器通信仅通过本地标准输入输出
快速参考
守护进程模式(推荐用于多次查询)
bash
启动守护进程(阻塞运行,在后台或单独终端中运行)
python3 scripts/lsp_client.py daemon --lang typescript --root /path/to/project --port 9876
使用认证令牌启动(请求需要 Bearer 令牌)
python3 scripts/lsp_client.py daemon --lang typescript --root /path/to/project --port 9876 --token mysecret
通过 HTTP 查询(所有输出为 JSON)
curl -s http://127.0.0.1:9876/goto -d {file:src/main.ts,line:10,col:5}
curl -s http://127.0.0.1:9876/refs -d {file:src/main.ts,line:10,col:5}
curl -s http://127.0.0.1:9876/hover -d {file:src/main.ts,line:10,col:5}
curl -s http://127.0.0.1:9876/diag -d {file:src/main.ts}
curl -s http://127.0.0.1:9876/symbols -d {query:UserService}
curl -s http://127.0.0.1:9876/shutdown
curl -s http://127.0.0.1:9876/ping # 健康检查
使用认证令牌
curl -s -H Authorization: Bearer mysecret http://127.0.0.1:9876/ping
单次模式
| 操作 | 命令 |
|---|
| 跳转到定义 | python3 scripts/lspclient.py --lang ts --root <dir> goto --file <f> --line <n> --col <n> |
| 查找引用 |
python3 scripts/lspclient.py --lang ts --root refs --file --line --col |
| 悬停类型信息 | python3 scripts/lsp_client.py --lang ts --root hover --file --line --col |
| 诊断 | python3 scripts/lsp_client.py --lang ts --root diag --file |
| 符号搜索 | python3 scripts/lsp_client.py --lang ts --root symbols [--query ] |
语言简写:--lang typescript 或 --lang ts 或 --lang python。
输出格式
所有命令输出 JSON 格式,便于结构化消费:
json
// goto / refs
[{file: /path/to/file.ts, line: 42, col: 10}]
// hover
function foo(bar: string): number
// diag
[{severity: ERROR, line: 10, message: Cannot find name x}]
// symbols
[{name: UserService, kind: 5, file: /path/to/file.ts, line: 12, container: models}]
何时使用 vs Grep
| 场景 | 使用 |
|---|
| 这个函数在哪里定义? | LSP goto |
| 谁调用了这个函数? |
LSP refs |
| 这个变量是什么类型? | LSP hover |
| 有编译错误吗? | LSP diag |
| 查找所有匹配 X 的类 | LSP symbols |
| 搜索文本模式 TODO | grep |
| 查找所有 .json 文件 | glob |
| 跨非代码文件搜索 | grep |
支持的语言
| 语言 | 服务器 | 安装方式 |
|---|
| TypeScript/JavaScript | typescript-language-server | npm i -g typescript-language-server typescript |
| Python |
pyright | pip install pyright |
添加语言:编辑 lsp_client.py 中的 SERVERS 字典 —— 添加服务器命令和文件扩展名。