消除大模型幻觉,让AI-IDE真正理解代码,打通LSP与AI的任督二脉
当我看到 Cursor 等 AI-IDE 的日志里显示 Grepping for...
时,我心里"咯噔"一下。
Grepping
?这不就是文本搜索吗!这能靠谱吗?如果大量变量名重名的话,那他不就乱套了吗?
Grep 是什么?
grep
是一个源于 Unix 的强大命令行工具,用于在文件中进行基于"正则表达式"的文本搜索。它非常快,但不"理解"代码的语法结构。AI 助手使用它,就像用 Ctrl+F 在代码的"字符串"海洋里捞针,捞上来的可能是鱼,也可能是靴子。
在大型项目开发中,当你让 AI-IDE 修改你的代码时,它经常会写很多错误类型,对复杂的类型继承关系更是一头雾水。这是多个原因造成的:
- 这个 AI-IDE 太懒了,仅仅读取了部分文件,上下文不足时,他会基于以往的经验猜测,这部分数据大多来自预训练的数据,或者你以前的上下文进行猜测。
- 使用
Grep
正则搜索时,如果遇到重名的变量名,此时就只能看 LLM 的智商能不能分辨了。
这种感觉,就像你给了助理一张藏宝图,他却只会按图上的文字去"搜索"地名,而完全不理解等高线、比例尺和指南针。
真正能"看懂"代码的,是 VSCode 背后那个强大的大脑------语言服务协议(Language Server Protocol, LSP)。
而 AI 模型与外部工具沟通,则依赖另一套语言------模型上下文协议(Model Context Protocol, MCP)。
于是一个想法在我脑中萌生:我能不能搭建一座桥梁,让 AI-IDE 的 MCP "嘴巴",能直接连上 VSCode 的 LSP "大脑"?
-
VSCode Marketplace : marketplace.visualstudio.com/items?itemN...
-
核心功能演示:
📖 阅读本文,你将收获
我不想只展示一个项目,更希望分享整个探索过程中的思考与沉淀。跟随我的脚步,你将了解到:
- 🤔 LSP vs Grep: 深入理解为什么 LSP 是现代 IDE 的基石,以及它如何从根本上超越传统的文本匹配。
- 🤝 MCP 协议解密: 了解 MCP 如何像"通用语"一样,让不同的 AI 模型与外部工具顺畅交流。
- 🌉 "造轮子"的艺术: 了解我是如何分析现有工具的优劣,并最终决定自己动手,打造一个更优的解决方案。
- 🚀 VSCode 插件开发心法 : 从环境搭建、调试到打包发布,我将分享基于
antfu/starter-vscode
模板的全流程实战经验。
科普:LSP 与 MCP,驱动 AI 编程的两大引擎
首先解释一下常用名词:
- LSP: Language Server Protocol,即语言服务协议,是 VSCode 的底层协议,用于与语言服务器通信。
- MCP: Model Context Protocol,即模型上下文协议,是 AI 模型与外部工具通信的协议。
- stdio: Standard Input Output,标准输入输出,是进程间通信的一种方式,也是 MCP 的默认通信方式。
- SSE : Server-Sent Events,服务器发送事件,是 HTTP 的一种通信方式,在 MCP 中是弃用状态,改用 Streamable HTTP 代替。
- Streamable HTTP: 流式 HTTP,是 HTTP 的一种通信方式,在 MCP 中是新标准,用于替代 SSE。
- LLM: Large Language Model,即大语言模型,是 AI 模型的统称。
要理解这个插件的价值,我们必须先弄懂它所连接的两个世界:LSP 和 MCP。
LSP:代码世界的"语法导航"
在没有 LSP 的世界里,每个编辑器(VSCode, Sublime, Atom...)想要支持一门新语言(Python, Rust, Go...),都得自己从头写一套代码分析引擎。这是一个"M x N"的灾难。
LSP (语言服务协议) 就是为了解决这个问题而生的。它将"编辑器 UI"和"语言分析服务"进行了解耦。
-
语言服务器 (Server): 一个独立的进程,它"精通"某一门语言,负责代码分析、错误检查、自动补全等所有"脏活累活"。
-
编辑器 (Client): 只需要实现 LSP 客户端,就能通过标准化的 JSON-RPC 协议与任何语言服务器通信,从而"免费"获得该语言的智能支持。
graph TD subgraph "VSCode 编辑器 (Client)" A[UI 界面] --> B[LSP Client]; end subgraph "语言服务器 (Server)" D[LSP Server] --> E[代码 AST 分析]; E --> F[符号定义/引用]; E --> G[类型推断]; end B <--> D; style B fill:#f60,stroke:#333,stroke-width:2px; style D fill:#b6b,stroke:#333,stroke-width:2px;
这就是为什么 LSP 比 grep
强大得多:LSP 理解代码的上下文和语义 ,而 grep
只认识字符串。
MCP:AI 与工具的"通用翻译器"
与 LSP 解决编辑器与语言的"M x N"问题类似,MCP (模型上下文协议) 旨在解决"AI 模型"与"外部工具"之间的"N x M"集成难题。
在 MCP 出现之前,如果你想让 Claude 和 ChatGPT 都能使用你的 API,你可能需要为它们各自编写不同的适配器。如果我没记错的话,最早出现的应该 Function-Call
Function-Call
下面我带你快速了解 Function-Call 的原理。
- 打开 aistudio.google.com/ 需要科学上网
- 点击右边的 Function Calling ,创建一个函数,实际就是 JSON 描述而已,我这里是计算两数之和,没什么高大上的,都是很简单的原理。注意要用英文描述,否则 Gemini 会报错。
- 询问你的问题,比如
996+770等于几?
,此时他会把你的问题转换成 JSON 格式,然后调用你创建的函数,并返回结果。但是这里是模拟,你需要自己填入结果 - 查看结果\
MCP
其实 MCP、Function-Call 都是一样的,很简单的原理。
- 定义 schema(即数据结构),任何语言都行,但是大家都爱 JSON
- 大模型自动提取对话的 参数,使用你的 schema,包装成合法的 JSON 格式,发送给你
- 你自己解析 JSON,然后 LLM(Large Language Model)会返回结果,此时 LLM 就能看到你返回的参数了。这样一来,LLM 就有无限的可能了
MCP 提供了一个统一的、供应商无关的接口,让任何 AI 模型都能通过一套标准协议来发现和调用外部工具。它就像一个通用翻译器,让说不同"方言"(来自不同公司)的 AI,都能和你这个"工具提供方"顺畅交流。
就跟你调用后端的接口,他给你数据一个道理。整个世界都是建立在通信(TCP/IP)之上的。
架构抉择:为什么选择 VSCode 插件?
在构思这个项目时,我面临一个关键的架构选择:是开发一个 VSCode 插件 ,还是创建一个独立的、通过命令行启动的 LSP 服务?
乍一看,后者似乎更"纯粹"。社区为几乎每种主流语言都提供了独立的 LSP 实现,例如 typescript-language-server
用于 TypeScript,或 pylsp
用于 Python。理论上,我可以将这些服务包装起来,通过 MCP 暴露给 AI。
但深入思考后,我发现了这种方法的几个致命缺陷:
- 巨大的资源浪费 : 启动一个独立的 LSP 服务,意味着它需要从头开始扫描、解析和索引整个项目的文件。这个过程不仅消耗大量的 CPU 和内存,而且完全是重复劳动------因为你正在使用的 VSCode,其内置的 LSP 客户端也正在做着一模一样的事情!
- 高昂的开发与维护成本: 如果要支持多种语言,我就需要集成和管理多个不同的 LSP 服务,处理它们各自的环境依赖和版本兼容性问题。这会让我的"小桥"变成一个复杂的"交通枢纽",开发和维护成本会急剧上升。
- 与 IDE 脱节: 独立服务无法轻易访问 VSCode 的工作区状态、用户配置等上下文信息,体验上会有割裂感。
相比之下,开发 VSCode 插件的优势则显而易见:
- 零资源冗余 : 插件直接复用 VSCode 已经建立 的 LSP 连接和分析结果。AI 通过插件查询时,相当于直接向 VSCode 的"大脑"提问,无需任何额外的文件分析开销。
- 开箱即用的多语言支持 : 只要 VSCode 能识别某个语言(通过安装对应的语言插件),我的插件就能为该语言提供 LSP 服务。我无需为
TypeScript
,Python
,Go
,Rust
等每一种语言做任何特殊适配。 - 轻量且专注: 插件的职责变得非常纯粹------仅仅是作为 VSCode 内部命令和 MCP 协议之间的"翻译官"。这大大降低了开发复杂度和维护成本。
这个决策,奠定了项目的基石:与其在 IDE 之外重建一个重复、笨重的轮子,不如聪明地利用 IDE 自身强大的能力。
挫败的探索:为什么最终决定"造轮子"?
确定了"VSCode 插件"这条路后,我满怀信心地认为市面上肯定有现成的解决方案。我的确找到了几个类似的工具,比如用 Python 编写的 Serena
(尽管它不是 VSCode 插件,也印证了我对独立服务的担忧)。
然而,我的尝试过程却异常坎坷。我发现这些工具普遍存在一些问题:
- 环境臃肿 : 比如
Serena
需要完整的 Python 环境,对于一个只想增强 VSCode 功能的用户来说,这个依赖太重了。 - 架构笨重: 它们大多作为独立的后台服务运行,需要用户手动管理其生命周期,而不是像 VSCode 插件一样"即开即用,即关即停"。
- 功能冗余: 提供了太多与 AI IDE 本身重叠的功能,不够专注,反而增加了复杂性。
- "失灵": 这是最致命的。我尝试了 4 个不同的工具,尽管 MCP 是通用的,但是它们的描述都是 Claude 如何使用,我在 Cursor 中配置后,LSP 相关功能无一例外地失败了。
在花费了大量时间却一无所获后,我意识到,与其在这些不可靠的"旧桥"上修修补补,不如自己从头建一座新的。我的目标很明确:
- 纯粹: 只做一件事------桥接 LSP 和 MCP。
- 轻量: 以 VSCode 插件形式存在,无额外依赖。
- 可靠: 专注核心 LSP 功能,确保它在所有支持 MCP 的 AI 工具中都能稳定运行。
探索之旅:从 0 到 1 的 VSCode 插件开发
从一个想法到能在 VSCode Marketplace 上被千万人使用的插件,这个过程比想象中更简单。VSCode 为开发者提供了完善的工具链和丝滑的开发体验。
环境搭建与项目初始化
虽然可以通过官方的 yo code
生成器从零开始,但我更推荐直接使用社区维护的优秀模板,例如 antfu/starter-vscode
。
bash
git clone https://github.com/antfu/starter-vscode.git my-lsp-plugin
cd my-lsp-plugin
pnpm install
这为我节省了大量花在工程配置上的时间,让我能立刻专注于核心逻辑的开发。按下 F5
,一个包含插件的"扩展开发宿主"窗口就会立刻启动,开启你的调试之旅。
插件核心:package.json
与 vscode
API
一个 VSCode 插件的核心是 package.json
清单文件和 vscode
全局 API。
-
package.json
: 它不仅仅是 Node.js 的依赖描述文件,更是插件的"户口本"。name
,publisher
,version
: 插件的身份标识。engines.vscode
: 声明插件兼容的 VSCode 版本。activationEvents
: 极其重要。它告诉 VSCode 在什么"时机"激活你的插件,避免不必要的性能开销。例如,只在打开某个类型的文件,或执行某个命令时才加载插件。contributes
: 插件的"贡献点",定义了插件向 VSCode 添加了哪些新功能,比如新的命令、设置、侧边栏视图等。
-
vscode
API : 插件的"瑞士军刀"。vscode
模块提供了海量的 API,让你能与编辑器的方方面面交互。vscode.window
: 用于创建和管理通知、状态栏、输入框等 UI 元素。vscode.workspace
: 提供对当前工作区文件、配置的访问能力。vscode.commands.executeCommand
: 这是一个强大的"传送门"。VSCode 将其海量的内部能力都通过"命令"的形式暴露出来。我们不需要知道"查找定义"的具体实现,只需要像调用一个函数一样,向 VSCode 发出指令即可。
比如,在本项目中,实现 textDocument/definition
(查看符号定义)功能的核心代码就是如此:
传入:
- 文件路径
- 行号
- 列号
返回:
- 详细符号信息
typescript
const definitions = await vscode.commands.executeCommand<vscode.Location[]>(
'vscode.executeDefinitionProvider', // "VSCode,帮我执行'定义提供者'"
document.uri, // 在哪个文件
position, // 在哪个位置
)
只需要完成所有的 LSP 功能,然后就能通过 MCP 服务调用了
调试与发布
-
调试 : 只需在代码中打上断点,然后按下
F5
。VSCode 会自动启动一个"扩展开发宿主"新窗口,我们的插件就在其中运行。所有在主窗口中的调试操作(单步执行、变量悬停、查看调用栈)都能无缝作用于插件代码。 -
打包与发布 : 当插件开发完毕,
vsce
(Visual Studio Code Extensions)这个命令行工具让分发变得异常简单。- 打包 : 运行
vsce package --no-dependencies
,一个.vsix
格式的插件包就生成了。 - 发布 : 首先,你需要在 Azure DevOps 上创建一个组织并获取一个"个人访问令牌 (PAT)",然后在 VSCode Marketplace 上创建"发布者"。最后,通过
vsce login <发布者ID>
登录,再用vsce publish
就可以将你的插件发布到全世界了!
- 打包 : 运行
如果需要让 Cursor 也能使用,需要发布到 open-vsx.org/
深入 MCP:构建你自己的"工具集"
光有 LSP 作为 IDE 的桥梁还不够,我们还需要了解如何让 AI 使用我们的工具。
所以需要开发一个 MCP 服务。
MCP 开发三步走
假设我们要将 VSCode 的 LSP 功能封装成 MCP 工具,让 AI 可以查询。
-
安装依赖 : 我们需要 MCP 的 TypeScript SDK 和
zod
用于数据校验。bashpnpm add @modelcontextprotocol/sdk zod
-
定义工具 : 使用
zod
定义工具的输入,这能确保 AI 传入的数据格式是正确的,并能提供清晰的描述。typescriptimport { z } from 'zod' const lspInputSchema = { uri: z.string().describe('目标文件的 URI'), line: z.number().describe('行号'), character: z.number().describe('字符位置'), }
-
创建服务器并注册工具 : 我们用
@modelcontextprotocol/sdk
创建一个 MCP 服务器,并将刚刚定义的工具注册进去。typescriptimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' const server = new McpServer({ name: 'lsp-mcp-server', version: '1.0.0', }) server.registerTool( 'get_hover', { title: '获取悬停信息', description: '根据文件 URI 和位置,获取代码符号的悬停提示信息 (类似 VSCode 中的鼠标悬停)', inputSchema: lspInputSchema, }, async (input) => { // 核心逻辑:调用 VSCode API const result = await getHoverFromVSCodeAPI(input.uri, input.line, input.character) return { content: [{ type: 'text', text: JSON.stringify(result) }] } } ) // ... 启动服务器的代码
MCP 支持多种通信方式,如
stdio
(用于本地进程通信)和Streamable HTTP
(用于网络服务)。 本项目采用后者,通过一个本地 HTTP 服务器暴露 MCP 端点。
调试 MCP 服务
调试 MCP 的最佳工具是官方提供的 @modelcontextprotocol/inspector
。它是一个 Web UI,可以连接到你的 MCP 服务器,让你直观地测试工具、查看请求和响应。
使用 @modelcontextprotocol/inspector
MCP Inspector 是一个专门用于测试和调试 MCP 服务器的开发工具。它由两个主要组件组成:
- MCP Inspector 客户端 (MCPI):基于 React 的 Web UI
- MCP 代理 (MCPP):Node.js 服务器,充当协议桥梁
安装和启动 Inspector
使用 npx 快速启动:
bash
npx @modelcontextprotocol/inspector
检查特定的 MCP 服务器:
bash
npx @modelcontextprotocol/inspector node build/index.js
Inspector 的代理负责连接 Web UI 和您的 MCP 服务器 。出于安全考虑,Inspector 代理服务器默认需要身份验证。当 Inspector 启动时,会生成一个随机会话令牌并打印到控制台 。
为方便起见,Inspector 会自动在 URL 中预填充此令牌打开浏览器(例如,http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=...),确保无缝的初始连接。如果 Inspector 已经打开,您可以手动将"代理会话令牌"输入到侧边栏的"配置"按钮中,然后单击"保存"以应用设置
打通任督二脉:在 AI 助手中配置 MCP
打开插件市场(按下 Ctrl + Shift + X
)搜索:LSP MCP,安装并启动后,会在本地启动一个 MCP 服务。
-
确认服务地址 : 插件默认在
http://localhost:9527/mcp
提供服务。你可以在 VSCode 的设置中搜索lsp-mcp.port
来修改端口。 -
配置 AI 助手: 打开 AI 助手的设置,找到 MCP 的配置选项
Cursor
json
{
"mcpServers": {
"lsp": {
"url": "http://127.0.0.1:9527/mcp"
}
}
}
Roo Code
json
{
"mcpServers": {
"lsp": {
"type": "streamable-http",
"url": "http://127.0.0.1:9527/mcp",
"disabled": false
}
}
}
局限性:这并非银弹
需要坦诚的是,这个插件并非万能。它的核心价值在于为 AI 提供了精准的代码符号导航与理解能力。
- 它擅长 :
查找定义
、查找引用
、悬停提示
、符号重命名
。在这些场景下,它的准确度远超任何基于文本搜索的方案。 - 它不擅长: 直接生成大段的业务逻辑。它的作用是为 AI 提供更精确的"地图",让 AI 在你已有的代码上进行分析和修改时,不会"迷路"。
在少数但关键的场景下,它能发挥巨大的作用,显著提升 AI 编程助手的"智商"。