消除大模型幻觉,让AI-IDE真正理解代码,打通LSP与AI的任督二脉

消除大模型幻觉,让AI-IDE真正理解代码,打通LSP与AI的任督二脉

当我看到 Cursor 等 AI-IDE 的日志里显示 Grepping for... 时,我心里"咯噔"一下。

Grepping?这不就是文本搜索吗!这能靠谱吗?如果大量变量名重名的话,那他不就乱套了吗?

Grep 是什么? grep 是一个源于 Unix 的强大命令行工具,用于在文件中进行基于"正则表达式"的文本搜索。它非常快,但不"理解"代码的语法结构。AI 助手使用它,就像用 Ctrl+F 在代码的"字符串"海洋里捞针,捞上来的可能是鱼,也可能是靴子。

在大型项目开发中,当你让 AI-IDE 修改你的代码时,它经常会写很多错误类型,对复杂的类型继承关系更是一头雾水。这是多个原因造成的:

  1. 这个 AI-IDE 太懒了,仅仅读取了部分文件,上下文不足时,他会基于以往的经验猜测,这部分数据大多来自预训练的数据,或者你以前的上下文进行猜测。
  2. 使用 Grep 正则搜索时,如果遇到重名的变量名,此时就只能看 LLM 的智商能不能分辨了。

这种感觉,就像你给了助理一张藏宝图,他却只会按图上的文字去"搜索"地名,而完全不理解等高线、比例尺和指南针。

真正能"看懂"代码的,是 VSCode 背后那个强大的大脑------语言服务协议(Language Server Protocol, LSP)

而 AI 模型与外部工具沟通,则依赖另一套语言------模型上下文协议(Model Context Protocol, MCP)

于是一个想法在我脑中萌生:我能不能搭建一座桥梁,让 AI-IDE 的 MCP "嘴巴",能直接连上 VSCode 的 LSP "大脑"?


📖 阅读本文,你将收获

我不想只展示一个项目,更希望分享整个探索过程中的思考与沉淀。跟随我的脚步,你将了解到:

  • 🤔 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 的原理。

  1. 打开 aistudio.google.com/ 需要科学上网
  2. 点击右边的 Function Calling ,创建一个函数,实际就是 JSON 描述而已,我这里是计算两数之和,没什么高大上的,都是很简单的原理。注意要用英文描述,否则 Gemini 会报错。
  3. 询问你的问题,比如 996+770等于几?,此时他会把你的问题转换成 JSON 格式,然后调用你创建的函数,并返回结果。但是这里是模拟,你需要自己填入结果
  4. 查看结果\
MCP

其实 MCP、Function-Call 都是一样的,很简单的原理。

  1. 定义 schema(即数据结构),任何语言都行,但是大家都爱 JSON
  2. 大模型自动提取对话的 参数,使用你的 schema,包装成合法的 JSON 格式,发送给你
  3. 你自己解析 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 插件,也印证了我对独立服务的担忧)。

然而,我的尝试过程却异常坎坷。我发现这些工具普遍存在一些问题:

  1. 环境臃肿 : 比如 Serena 需要完整的 Python 环境,对于一个只想增强 VSCode 功能的用户来说,这个依赖太重了。
  2. 架构笨重: 它们大多作为独立的后台服务运行,需要用户手动管理其生命周期,而不是像 VSCode 插件一样"即开即用,即关即停"。
  3. 功能冗余: 提供了太多与 AI IDE 本身重叠的功能,不够专注,反而增加了复杂性。
  4. "失灵": 这是最致命的。我尝试了 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.jsonvscode 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(查看符号定义)功能的核心代码就是如此:

传入:

  1. 文件路径
  2. 行号
  3. 列号

返回:

  1. 详细符号信息
typescript 复制代码
const definitions = await vscode.commands.executeCommand<vscode.Location[]>(
  'vscode.executeDefinitionProvider', // "VSCode,帮我执行'定义提供者'"
  document.uri, // 在哪个文件
  position,     // 在哪个位置
)

只需要完成所有的 LSP 功能,然后就能通过 MCP 服务调用了

调试与发布

  • 调试 : 只需在代码中打上断点,然后按下 F5。VSCode 会自动启动一个"扩展开发宿主"新窗口,我们的插件就在其中运行。所有在主窗口中的调试操作(单步执行、变量悬停、查看调用栈)都能无缝作用于插件代码。

  • 打包与发布 : 当插件开发完毕,vsce(Visual Studio Code Extensions)这个命令行工具让分发变得异常简单。

    1. 打包 : 运行 vsce package --no-dependencies,一个 .vsix 格式的插件包就生成了。
    2. 发布 : 首先,你需要在 Azure DevOps 上创建一个组织并获取一个"个人访问令牌 (PAT)",然后在 VSCode Marketplace 上创建"发布者"。最后,通过 vsce login <发布者ID> 登录,再用 vsce publish 就可以将你的插件发布到全世界了!

如果需要让 Cursor 也能使用,需要发布到 open-vsx.org/


深入 MCP:构建你自己的"工具集"

光有 LSP 作为 IDE 的桥梁还不够,我们还需要了解如何让 AI 使用我们的工具。

所以需要开发一个 MCP 服务。

MCP 开发三步走

假设我们要将 VSCode 的 LSP 功能封装成 MCP 工具,让 AI 可以查询。

  1. 安装依赖 : 我们需要 MCP 的 TypeScript SDK 和 zod 用于数据校验。

    bash 复制代码
    pnpm add @modelcontextprotocol/sdk zod
  2. 定义工具 : 使用 zod 定义工具的输入,这能确保 AI 传入的数据格式是正确的,并能提供清晰的描述。

    typescript 复制代码
    import { z } from 'zod'
    
    const lspInputSchema = {
      uri: z.string().describe('目标文件的 URI'),
      line: z.number().describe('行号'),
      character: z.number().describe('字符位置'),
    }
  3. 创建服务器并注册工具 : 我们用 @modelcontextprotocol/sdk 创建一个 MCP 服务器,并将刚刚定义的工具注册进去。

    typescript 复制代码
    import { 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 服务器的开发工具。它由两个主要组件组成:

  1. MCP Inspector 客户端 (MCPI):基于 React 的 Web UI
  2. 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 服务。

  1. 确认服务地址 : 插件默认在 http://localhost:9527/mcp 提供服务。你可以在 VSCode 的设置中搜索 lsp-mcp.port 来修改端口。

  2. 配置 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 编程助手的"智商"。

相关推荐
倔强青铜三42 分钟前
PyCharm正在慢性死亡?VSCode碾压式逆袭!
python·pycharm·visual studio code
一涯18 小时前
自己写一个vscode插件
前端·visual studio code
AmsWait21 小时前
为Github Copilot创建自定义指令/说明/注意事项
ai编程·visual studio code·github copilot
SugarPPig1 天前
使用的IDE没有内置MCP客户端怎么办?
ide·mcp
ffutop1 天前
MCP 能力探索
mcp
带刺的坐椅1 天前
Solon v3.4.2(Java 应用开发生态基座)
java·ai·solon·liteflow·mcp
hsfxuebao2 天前
Cursor快速上手+科学使用指南
cursor
青衫客362 天前
LLM—— 基于 MCP 协议(Stdio 模式)的工具调用实践
大模型·llm·mcp
友莘居士2 天前
本地使用postman调试mcp接口
测试工具·postman·sse·mcp