JetBrains Index MCP 源码解析:让 AI 直接使用 IDE 的代码分析能力
一、它解决的到底是什么问题
在一个有一百多个模块的 Java/Kotlin 工程里,AI 不仅需要找到代码,还需要确认代码之间的关系:某个调用指向哪个重载方法、某个接口有哪些实现、某个公共方法的调用者分布在哪些模块。
grep 和 ripgrep 擅长文本匹配。它们可以找到 save,但不能独立判断这个 save 属于订单仓库还是图片缓存。AI 可以继续读声明、导入、类型和继承关系来判断,只是需要更多步骤。
这个插件把 JetBrains 已有的代码导航、索引、语言解析与重构能力包装成 MCP 工具。AI 可以直接请求“查这个方法的引用”,把一部分代码关系判断交给 IDE 完成。
它的价值因此不只是搜索速度,还包括减少无关上下文、降低同名误判、辅助影响分析。它也不能替代业务理解或保证发现所有运行时调用。
二、部署形态:服务器就在 IDE 进程内
这是一个 JetBrains 插件,内部嵌入 HTTP MCP 服务。通常不需要再部署独立后端、向量数据库或额外的代码索引服务。
AI 编程客户端
│ MCP / JSON-RPC / HTTP
▼
IDE 进程内的 Ktor CIO 服务器
│ 工具分发、项目选择、参数检查
▼
IntelliJ Platform + 对应语言插件
│ PSI、索引、引用解析、重构 API
▼
IDE 已加载的工程、模块、依赖
2.1 安装与连接
在 IDE 插件市场安装
IDE Index MCP Server,按提示重启。打开目标工程,等待 Gradle/Maven 导入和索引完成。
在
Settings → Tools → Index MCP Server查看端口、工具开关等设置。将工具窗口提供的 MCP URL 配置到客户端;也可以使用插件的
Install on Coding Agents功能。
默认连接地址如下:
支持 mcpServers 和 url 配置格式的客户端可参考:
{
"mcpServers": {
"intellij-index": {
"url": "http://127.0.0.1:29170/index-mcp/streamable-http"
}
}
}
不同客户端的配置文件和字段可能不同,应以插件生成的对应客户端配置为准。IDEA 和 Android Studio 是不同进程、不同服务;连接某个服务,不意味着能查询另一个 IDE 中的工程。
当前源码要求 IntelliJ Platform 2025.3 或更高版本,JVM 21 或更高版本。自行构建插件可使用 JDK 21 执行 ./gradlew buildPlugin,然后从 build/distributions/ 选择 ZIP,通过 IDE 的“从磁盘安装插件”安装。./gradlew runIde 用于启动开发沙箱。
默认监听 127.0.0.1,没有身份认证。源码有 Origin/Host 防护,但它不等于身份认证;如改成远程监听,需要另行处理访问控制。重构工具会修改源码。
2.2 源码中的启动过程
plugin.xml 注册 McpServerStartupActivity 为项目启动活动。打开项目时,它通知 ProjectResolver、初始化构建诊断缓存,然后调用 McpServerService.initialize()。
McpServerService 是应用级服务,即同一个 IDE 实例中的多个项目共享一个服务。它初始化工具注册表和语言处理器,读取设置并启动 KtorMcpServer,也负责停止、重启和异常停止后的恢复。
KtorMcpServer 使用 embeddedServer(CIO, port, host)。主要提供无状态 Streamable HTTP,同时保留旧版 SSE 接口。主要接口不要求客户端保存 Mcp-Session-Id。
注意:HTTP 传输无状态,不代表插件完全无状态。分页缓存、符号句柄、项目状态仍保存在 IDE 服务中,会受到服务重启和代码变化的影响。
三、一个 MCP 请求如何到达 IDEA
协议与业务分为两层:MCP SDK 负责 initialize、tools/list、tools/call 等协议行为;插件负责具体工具执行。
POST /index-mcp/streamable-http
→ MCP SDK 解析 JSON-RPC
→ McpServerFactory 注册的工具回调
→ McpToolDispatcher.call()
→ ProjectResolver 选择工程
→ AbstractMcpTool.execute()
→ FindClassTool / FindUsagesTool / CallHierarchyTool ...
→ IntelliJ API / 语言专用 Handler
→ 序列化结果返回客户端
McpServerFactory 为无状态 HTTP 请求创建 MCP Server 对象。注册工具时,会根据设置过滤禁用工具,因此 tools/list 不会展示所有源码中存在的功能。关闭工具后,直接调用也会被分发层拒绝。
McpToolDispatcher 主要做以下工作:
确认工具存在、已启用,并检查 IDE UI 线程是否失去响应。
根据
project_path或符号句柄所属项目路由请求。记录命令历史、执行结果和耗时。
在 IntelliJ 所需的执行上下文中运行工具。
对普通工具设置默认 55 秒执行超时;部分长操作自行管理等待预算。
将工具执行失败转成
isError: true,区别于 JSON-RPC 协议错误。
AbstractMcpTool 提供参数解析、目标定位、PSI 同步相关处理、索引状态检查和读写操作封装。具体工具通常在安全的读操作中访问 PSI;修改源码则需要相应的写操作机制。
整个过程直接调用 IDE 内部 API,不需要模拟鼠标点击、快捷键或读取屏幕。 “跳转定义”工具可以返回定义位置而不打开编辑器;显式打开文件是另一个工具的职责。
四、关键参数:先选工程,再选目标,再控制范围
4.1 选择工程
只打开一个工程时通常可以省略。对于支持 symbolId 的工具,省略项目路径时也可以由句柄路由到所属工程。
跨模块与跨独立项目需要区分:同一 IDE 工程中正确导入的模块可以通过项目模型解析依赖;在多个窗口打开多个仓库,并不会自动把它们合成一个统一语义工程。
4.2 选择具体类或方法
许多语义导航工具支持以下三种方式,选择一种即可:
line、column 从 1 开始。项目文件一般传项目相对路径;只读导航也支持插件返回的部分依赖库绝对路径或 jar:// 地址。位置应指向目标名称,例如 repo.save() 中的 save。
Java 符号示例为 com.example.OrderService#submit(String)。符号字符串语法依赖语言处理器,不能把 Java 语法直接套到所有语言。
支持统一目标的工具也可以使用嵌套结构:
{
"target": {
"position": {
"file": "order/src/main/java/com/example/OrderService.java",
"line": 42,
"column": 17
}
}
}
嵌套 target 与顶层的目标选择参数不要混用。最终以当前服务 tools/list 返回的 schema 为准。
4.3 控制范围和结果数量
当前版本中,查类默认排除生成代码;查引用、调用链默认包含生成代码。对于 Android 的 KSP、Dagger 等工程,这一点很重要:生成代码既可能制造噪声,也可能是有效引用的关键来源。
分页字段并不完全统一:列表查询通常返回 nextCursor,层级查询使用 cursor。需要根据具体响应继续请求,并关注 hasMore、stale、totalIsExact、truncationReason 等适用字段,不能只看当前页长度就断言结果完整。
五、查代码、查 class、查方法链路是如何实现的
这里需要区分两个概念:PSI(Program Structure Interface)是 IntelliJ 对程序结构的表示和访问接口,能表示类声明、方法、表达式与引用;索引则帮助 IDE 快速找到候选文件和符号。具体引用指向谁,还需要语言解析结合类型、作用域、导入和依赖关系确认。因此,它不是简单维护一张方法名列表,也不是依靠向量相似度检索代码。
5.1 查 class:复用 Go to Class 的索引入口
FindClassTool 使用 ChooseByNameContributor.CLASS_EP_NAME 扩展点搜索类与接口,并使用 IntelliJ 的名称匹配机制处理缩写、子串等查询。
典型参数为:
{
"project_path": "/work/company-app",
"query": "OrderService",
"language": "Java",
"matchMode": "exact",
"scope": "project_files",
"pageSize": 20
}
返回类名、全限定名称、文件位置、类型及可用的符号句柄等信息。matchMode 支持 substring、prefix、exact;默认是 substring。
这里的优势是查“类声明”,不会把注释里提到的同名文字一起当成类。
5.2 查定义:从引用解析到声明
FindDefinitionTool 先定位 PSI 元素,再通过 PsiUtils 等辅助逻辑解析实际目标。如果目标是依赖库,代码会尽量导航到源码,而不是停留在编译后的占位元素。
结果包含定义位置、名称、预览和 symbolId。定义预览并不等于读取完整文件,完整阅读可以接续文件读取工具。
symbolId 在服务中关联具体 PSI 目标,避免每一步都重新猜测行列或重载签名。它不是永久 ID,也不能靠比较 ID 字符串判断两个结果是否为同一声明。服务重启、项目关闭、目标删除、缓存淘汰等情况会使其失效,需要重新查询。
5.3 查源码和文件结构
这三个工具在该版本中默认禁用,需要在 Exposed Tools 中开启。文件结构默认返回文本树;设置 includeSymbolIds: true 会同时请求结构节点和句柄,可用于后续查方法引用。
{
"project_path": "/work/company-app",
"file": "order/src/main/java/com/example/OrderService.java",
"includeNodes": true,
"includeSymbolIds": true
}
对于大文件,可以先获取结构,再按节点位置读取局部源码,避免一次返回整份文件。依赖库可读内容取决于 IDE 能取得的源码或导航表示,不应假设任意 JAR 都带完整源代码。
5.4 查引用:按符号身份搜索
FindUsagesTool 的核心是 ReferencesSearch.search(targetElement, searchScope)。它不是搜索方法名字符串,而是向 IDE 请求目标元素的引用。
返回结果可包含文件、行列、上下文片段和引用类型。引用不仅包括方法调用,还可能是导入、字段访问等。因此“引用数量”和“调用者方法数量”不能直接比较。
{
"project_path": "/work/company-app",
"symbolId": "sym_REPLACE_WITH_REAL_METHOD_ID",
"scope": "project_production_files",
"pageSize": 50,
"includeGenerated": true
}
做全工程影响分析时,不宜一开始就用 paths 排除其他业务模块。需要聚焦时再限定目录。
5.5 查实现和继承
LanguageHandlerRegistry 根据可用语言插件加载处理器。Java/Kotlin 的实现集中在 JavaHandlers.kt,其中使用:
ClassInheritorsSearch查询类或接口的继承者。OverridingMethodsSearch查询方法的覆盖实现。父方法相关 API 查询方法继承关系。
对应工具包括 ide_find_implementations、ide_type_hierarchy、ide_find_super_methods。这解决了“实现藏在抽象父类或间接继承中”的查找问题,但列出实现不等于确定运行时依赖注入选择了哪个实现。
5.6 查调用链:向上找调用者,向下找被调用者
CallHierarchyTool 负责解析目标、参数和分页,再交给对应语言的调用关系处理器。
Java/Kotlin 的调用者查询先把目标解析为 PsiMethod,再搜索当前方法及选取的最上层父方法的引用,从引用所在位置定位调用者方法,并继续向上查询。源码用去重和已访问集合避免重复展开。
这里还有一个具体的 Kotlin 适配:suspend 函数在 JVM 方法表示中存在编译器增加的 Continuation 参数,只使用 MethodReferencesSearch 可能漏掉源码调用。因此实现会对 Kotlin 声明补充 ReferencesSearch,然后去重。
向下查 Java 调用时,实现遍历方法体内的 PsiMethodCallExpression,调用 resolveMethod() 解析被调用的方法,再递归展开;Kotlin 则有对应解析处理。
这是一种静态调用关系分析。例如实现方法的调用者查询会考虑父接口方法,因此结果可能包含通过接口发起的潜在调用,不能直接断言这些调用在运行时一定落到该实现。
此外,语法树中的调用表达式不等于实际执行顺序。条件分支、延迟执行的 lambda、异步调度、事件总线、反射和跨进程调用,都需要进一步结合源码、配置或运行时证据判断。
六、完整调用示例:从类定位到业务链路
正常情况下,MCP 客户端负责初始化和消息封装。协议过程可以理解为:
initialize → notifications/initialized → tools/list → tools/call
下面是一次完整的 JSON-RPC 工具调用,请求 IDEA 查找类:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "ide_find_class",
"arguments": {
"project_path": "/work/company-app",
"query": "OrderService",
"pageSize": 20
}
}
}
后续工具复用这个外层结构,替换 name 和 arguments 即可。推荐顺序如下:
调用
ide_index_status,确认不是 Dumb Mode,即 IDE 没有处于限制索引访问的阶段。调用
ide_find_class找到类,核对全限定名称和所在模块。调用
ide_file_structure获取方法节点,或按准确源码位置调用ide_find_definition,拿到目标方法的句柄。类句柄不能直接代替方法句柄。对接口调用
ide_find_implementations,明确候选实现。对目标方法调用
ide_call_hierarchy,向上追入口或向下追依赖。用
ide_read_file读取关键方法,确认业务条件和实际行为。用
ide_find_references补查直接使用位置,检查分页和截断标志。
例如对已经发现的方法查询上游调用:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "ide_call_hierarchy",
"arguments": {
"project_path": "/work/company-app",
"symbolId": "sym_REPLACE_WITH_REAL_METHOD_ID",
"direction": "callers",
"depth": 3,
"maxNodes": 50,
"scope": "project_files",
"includeGenerated": true
}
}
}
需要向下追踪时,将 direction 改成 callees。第一页没有涵盖全部结果时,按返回的 cursor 继续请求。不要无条件把深度拉满:公共底层方法的上游很容易扩展成庞大的关系图。
七、一百多个模块的工程,实际收益在哪里
假设需求是:“订单提交逻辑增加登录校验,先查清影响范围。”以下是工作方式对比,不是性能实测。
最直接的收益,是把 AI 花在“这个名字到底指谁”的工作转移给 IDE。AI 仍然负责理解业务,但能更快取得相关证据。
准确性收益尤其体现在同名方法、重载、间接继承和跨语言引用上。不过插件自己也有适配代码和边界。例如当前 Java 调用者实现对父方法数量有限制,传统调用树也存在每层数量限制;因此不能把“使用语义 API”理解为“结果永远穷尽”。
八、能否省时间和 token
时间:少往返,但不是每条查询都更快
索引已就绪时,IDE 可以直接利用项目模型解析符号,减少 AI 反复搜索、读取文件、确认类型的步骤。多模块影响分析更容易受益。
但 IDE 同步和首次索引需要时间、CPU 与内存。大量引用或深调用树也可能很慢。源码中的列表分页扩展有时需要重新执行搜索并跳过已见结果,因此分页主要控制输出量,不保证底层检索成本同比下降。
Token:少读无关代码,而不是索引本身省 token
本地扫描文件或计算索引不会直接消耗模型 token。真正影响 token 的,是送给模型的工具说明、参数、返回内容以及后续推理与交互。
语义查询可能减少无关匹配和为确认类型而读取的上下文;分页、局部文件读取、结构节点也能控制输出。插件还支持把结构化响应从 JSON 转成 TOON,适合部分重复字段较多的数据,但具体收益取决于内容和分词。
另一方面,工具 schema、JSON 字段和大调用树都有开销。如果 rg 只需返回两行即可回答,MCP 不一定更省。本文没有进行相同任务的耗时与 token 对照测试,不给出节省百分比。
要评估公司工程中的收益,可以选择几个固定任务,以同一提交、相同问题分别执行两套流程,记录完成耗时、工具往返次数、token 用量,并人工核对遗漏与误判。只比较一次搜索耗时,不能代表完成任务的总成本。
九、使用中的关键边界
项目模型必须正确。 Gradle 同步失败、依赖缺失、模块未导入时,语义查询结果可能不完整。
磁盘代码与 IDE 缓存要一致。 外部工具编辑后,必要时调用
ide_sync_files。自动同步外部变化选项默认关闭,开启可能带来性能成本;构建文件变化还可能需要重新同步项目模型。查不到不等于不存在。 先检查工具错误、搜索范围、索引状态、生成代码开关、分页及句柄有效性,再作判断。
语义查询不覆盖全部动态行为。 反射、依赖注入选择、事件分发、远程调用需要其他证据。
导航和重构能力不同。 查到引用不表示任意修改都能自动完成;使用重构工具后仍需检查 diff,并执行相关构建和测试。
工具可用性依赖版本和设置。 本地工具列表比网上 README 更能说明当前能调用什么,不同语言支持也不完全相同。
适合大型工程的组合是:用 rg 定位字符串、配置和日志;用 MCP 确认符号、继承和调用关系;用源码阅读解释业务;用构建、测试或运行时观测验证结论。
十、源码阅读索引
以下链接固定到本文分析提交,方便复核,避免后续 main 分支更新导致实现与文章不一致。
本文完成的是源码和文档分析,没有对公司百模块工程执行查询,也没有运行插件构建、性能基准或 token 对照实验。
