内容
# LSP
驱动一个 **语言服务器** (从 `sourcekit-lsp` 开始),并通过 **CLI** 和 **MCP 服务器** 公开其代码智能,以便 AI 代理可以调用。
`LSP` 建立在 [JSONFoundation](https://github.com/Cocoanetics/JSONFoundation) 的**统一 JSON-RPC 运行时**之上 —— 事实上,它是证明该运行时可以通用的测试平台。 LSP、 [ACP](https://github.com/Cocoanetics/SwiftACP) 和 [MCP](https://github.com/Cocoanetics/SwiftMCP) 都使用 stdio 进行 JSON-RPC;它们在 **一个** 轴上有所不同 —— LSP 使用类似 HTTP 的 `Content-Length` 头部帧,而 ACP/MCP 使用换行符分隔的 JSON。 JSONFoundation 2.1+ 拥有整个堆栈(对等方、两个帧编解码器、stdio 传输),因此 LSPKit 编写 **没有** 传输和 **没有** 对等方 —— 只是在上面添加了有类型的 LSP 方法:
```swift
import LSPKit
let client = try LSPClient(launch: LSPServer.sourceKit()) // 启动 sourcekit-lsp
await client.start()
_ = try await client.initialize(rootURI: LSPURI.file(projectDir))
try await client.initialized()
try await client.didOpen(path: file)
for symbol in try await client.documentSymbol(path: file) { print(symbol.name) }
await client.shutdownAndExit()
```
高级终端状态是 **MCP 服务器**:精确的、编译器级别的代码智能 —— “检查此文件是否存在错误”,“在哪里定义了 `Foo`”,“查找对此符号的每个引用” —— 作为工具提供给代理,而无需它自己读取和解析源代码。
---
## 内容
| 产品 | 描述 |
| --- | --- |
| **`LSPKit`** | 一个库:`LSPClient`(有类型的 LSP 生命周期 + 查询),`LSPServer`(启动描述符),以及 `LSP*` 值类型。 |
| **`lsp`** | 一个可执行文件,既是 **CLI** (`lsp symbols Foo.swift`),又是 **MCP 服务器** (`lsp mcp`)。 |
## 需求
- **macOS 14+** 和 **Swift 6.3** 工具链(**Xcode 26.4** 或更新版本)来构建。
(较旧的 SwiftPM 错误地处理了 SwiftMCP 图表中的一个特征条件依赖 —— 请参见 [`Package.swift`](Package.swift) 中的注释。)
- `sourcekit-lsp`,通过 `xcrun` 解析 —— 即已安装的 **Xcode** 或命令行工具。(其他服务器也适用 —— 请参见 [其他语言服务器](#其他语言服务器)。)
## 构建
```sh
git clone https://github.com/Cocoanetics/LSP.git
cd LSP
swift build # 或:swift build -c release
```
构建的 `lsp` 二进制文件位于 `.build/debug/lsp` (或 `.build/release/lsp`)。
---
## CLI
每个子命令都会启动一个新的 `sourcekit-lsp`,对封闭的 Swift 包执行 LSP 握手,回答问题,然后关闭服务器。
```sh
lsp where Foo # 在项目范围内查找名为“Foo”的符号(索引支持)
lsp decl Foo # 打印“Foo”的声明/签名
lsp check Sources/App/Foo.swift # 语法 + 语义错误,无需构建
lsp symbols Sources/App/Foo.swift # 文件的文档符号树
lsp hover Sources/App/Foo.swift 7 12 # 指定位置的悬停文本
lsp definition Sources/App/Foo.swift 11 30 # 跳转到定义的目標
lsp references Sources/App/Foo.swift 11 30 # 项目范围内所有对该符号的引用
lsp capabilities Sources/App/Foo.swift # 服务器宣传的功能
lsp mcp . # 作为 MCP 服务器运行(stdio)
```
| 命令 | 目的 | 显著选项 |
| --- | --- | --- |
| `where <query> [dir]` | 按名称在项目范围内查找符号(索引支持)。 | `--scope project\|dependencies\|all`, `--exact`, `--json` |
| `decl <query> [dir]` | 打印符号的声明(签名;带有 `--full` 的完整文档)。 | `--scope`, `--exact`, `--full`, `--json` |
| `check <file>` | 报告语法/语义错误(LSP 诊断),无需构建。 | `--json` |
| `symbols <file>` | 以层次结构列出文件的文档符号。 | |
| `hover <file> <line> <col>` | 指定位置的悬停文本(类型,签名,文档)。 | |
| `definition <file> <line> <col>` | 符号在指定位置的定义位置。 | |
| `references <file> <line> <col>` | 项目范围内所有对指定位置符号的引用。 | |
| `capabilities <file-or-dir>` | 打印服务器宣传的功能。 | |
| `mcp [dir]` | 作为 MCP 服务器运行(stdio 或 HTTP)。 | `--http-port`, `--http-host`, `--token` |
**位置参数为 0 基数**,列为 **UTF-16** 代码单元偏移 —— LSP 约定。打印的位置 (`path:line:col`) 和 `check --json` 为 **1 基数**,匹配编辑器显示的内容;`where --json`/`decl --json` 嵌入原始(0 基数)LSP 范围。(下面的 MCP 工具全部为 1 基数。)
`where`/`decl`/`check`/`references` 依赖于 `sourcekit-lsp` 的**后台索引**;在构建索引时,stderr 上会显示进度条,因此 stdout 上的 `--json` 保持干净且可管道化。
---
## MCP 服务器 —— 代理的代码智能
`lsp mcp [project-dir]` 作为 **MCP 服务器运行于 stdio**,由单个长期运行的 `sourcekit-lsp` 支持,其索引仅需构建一次,并在调用之间保持活跃。 将任何 MCP 客户端(Claude Code 等)指向它:
```jsonc
{
"mcpServers": {
"lsp": {
"type": "stdio",
"command": "/absolute/path/to/LSP/.build/release/lsp",
"args": ["mcp", "/absolute/path/to/your/project"]
}
}
}
```
在开发过程中,您可以跳过构建步骤,让 SwiftPM 运行它(此仓库附带一个 [.mcp.json](.mcp.json),对自己的目录执行此操作):
```jsonc
{ "mcpServers": { "lsp": { "type": "stdio",
"command": "swift", "args": ["run", "lsp", "mcp", "."] } } }
```
一个注意事项:在新克隆的仓库中,`swift run` 首先会构建整个依赖图 —— 比大多数 MCP 客户端的启动超时时间还长。 在第一次启动之前运行 `swift build`;之后,热重建速度足够快。
### HTTP
对于一个长期运行的服务器,外部客户端连接(而不是启动),请传递 `--http-port` 以通过 HTTP 服务,而不是 stdio —— 与 SwiftACP 的 `acpxd` 和 SwiftMCP 演示的形状相同:
```sh
lsp mcp /path/to/project --http-port 8080 # http://127.0.0.1:8080/mcp
lsp mcp /path/to/project --http-port 8080 --token secret # 需要 bearer token
lsp mcp /path/to/project --http-port 8080 --http-host 0.0.0.0 # 暴露在 loopback 之外
```
| 选项 | 含义 |
| --- | --- |
| `--http-port <port>` | 在此端口上通过 HTTP 服务,而不是 stdio。MCP 端点为 `http://<host>:<port>/mcp`(可流式 HTTP);旧版 SSE 客户端可使用 `/sse`。 |
| `--http-host <host>` | 绑定地址。默认为 `127.0.0.1`(loopback);如果您打算让它可以从其他机器访问,请传递 `0.0.0.0`。 |
| `--token <token>` | 在每个请求上都需要此 bearer token。**省略 ⇒ 未认证** —— 在 loopback 上没问题,但与 `--http-host 0.0.0.0` 结合使用时存在风险。 |
### 工具
| 工具 | 回答内容 |
| --- | --- |
| `check_file(file)` | 内存类型检查的语法 + 语义错误 —— 在支付完整构建费用之前验证编辑。 |
| `find_symbol(query, scope, exact)` | 将名称转换为文件位置,而无需读取文件。 |
| `declaration(query, scope, exact, includeDocumentation)` | 符号的签名(及其可选的文档注释),按名称查找。 |
| `hover(file, line, column)` | 指定位置的类型 / 签名 / 文档。 |
| `definition(file, line, column)` | 符号在指定位置的定义位置。 |
| `references(file, line, column)` | 项目范围内所有对指定位置符号的引用。 |
| `document_symbols(file)` | 文件中声明的类型/方法/属性,作为树结构。 |
**MCP 工具位置为 1 基数**(第 1 行 = 第一行,第 1 列 = 第一个字符),匹配编辑器显示的内容;它们在内部转换为 LSP 的 0 基数。
如果语言服务器宕机(崩溃,或外部 `kill`),会话会检测到退出并在下一个工具调用时重启,而不是提供一个已死亡的连接。
---
## 其他语言服务器
`sourcekit-lsp` 是默认的,但启动只是一个进程描述符。任何 LSP 服务器都可以使用:
```swift
let clangd = try LSPClient(launch: LSPServer.command("clangd"))
let pyright = try LSPClient(launch: LSPServer.command("pyright-langserver", ["--stdio"]))
```
`lspLanguageId(forPath:)` 已经将常见扩展名(`.swift`,`.m`,`.c`,`.cpp`,`.py`,...)映射到服务器期望的 `languageId`。
---
## 工作原理
线路格式是人们在使用原始 `sourcekit-lsp` 时容易出错的部分。它使用 HTTP 样式的头部帧,而不是换行符分隔的 JSON。
```
Content-Length: 123\r\n
\r\n
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{ … }}
```
管道原始 JSON 服务器会无限期等待。即使是帧格式,它也会在收到 `initialize` **请求** 后跟随 `initialized` **通知** 之前不执行任何操作。
[`probe.py`](probe.py) 是一个约 100 行的无依赖 Python 脚本,演示了整个流程(帧 → 握手 → `didOpen` → `documentSymbol` → 关闭)。它是对 Swift 实现进行验证的参考;运行 `python3 probe.py` 针对此仓库以实时查看。
在 Swift 中,整个运行时 —— 请求/响应相关性、入站调度、`Content-Length` 帧和 `Foundation.Process` stdio 传输 —— 来自 JSONFoundation:
```
JSONFoundation JSONValue · JSONRPCMessage 信封
JSONRPCPeer 传输无关的相关器 + 分发器
JSONRPCWire 帧编解码器 — ContentLengthFraming 是 LSP 的线路格式
JSONRPCStdio ProcessTransport — 一个零依赖的 Process stdio 传输
```
LSPKit 是 `JSONRPCPeer(transport: ProcessTransport(launch:, framing: ContentLengthFraming()))` 加上有类型的 LSP 方法。 有关设计叙述和路线图,请参见 [GOALS.md](GOALS.md)。
---
## 项目布局
```
Sources/LSPKit/ 库 — LSPClient,LSPServer,LSP* 模型,查询支持
Sources/lsp/ 可执行文件
Commands/ 每个 CLI 子命令一个文件(where,decl,check,...)
Support/ 连接生命周期,索引监视器,输出格式化
LSPMCPServer.swift @MCPServer 公开工具
LSPSession.swift 温暖的、自我重启的 sourcekit-lsp,MCP 服务器持有
Tests/LSPKitTests/ 单元测试 + 实时 sourcekit-lsp 往返(如果不存在则跳过)
probe.py 一次性线路流程参考(Python)
```
## 许可证
BSD 2-Clause。 请参见 [LICENSE](LICENSE)。