Skip to content

dsh-intellij-mcp

Verified

dsh-intellij-mcp · v1.0.5 · MIT · Web UI

IntelliJ IDEA MCP bridge for DeepSeek Harness: connects to the IDE's built-in MCP server over stdio (idea stdioMcpServer) and registers its tools as mcp__intellij__*, with a settings page for the connection

Install

dsh plugin add dsh-intellij-mcp

Confirm the layer applied with dsh --profile default --dump-config — see the install guide.

Source

Published to npm without a public repository. Inspect the package contents before installing.

Tags

Creators

Readme

dsh-intellij-mcp

DeepSeek Harness(DSH)插件:把 IntelliJ IDEA 内置的 MCP 服务器接入 DSH,让 Agent 能直接读写 IDE 里打开的项目、跑构建、做重构与检索,并在插件页提供可配置的连接设置。

安装后模型可用的工具形如 mcp__intellij__*;IDEA 的 MCP 服务器只暴露一个通用执行器,因此实际只有:

mcp__intellij__execute_tool   # 参数 command: "<工具名> --<参数> <值>"

前置条件(重要)

idea stdioMcpServer 不是独立的 MCP 服务器,而是一个 stdio ↔ SSE 代理:它把 stdin/stdout 上的 MCP 消息转发给已经在运行的那个 IDE 内部的 SSE 端口。

因此:

  1. IDE 必须先打开目标项目(本包默认未预置 projectPath,请按下方设置页填你自己的项目路径)。
  2. 端口必须等于 IDE 实际监听的端口。查法:
    ss -lntp | grep 64342
    
    IDE 重启后端口可能变化 —— 变了就在插件页改(见下)。
  3. 端口不对时的唯一症状是:
    io.ktor.client.plugins.sse.SSEClientException: Connection refused
    
    它永远不会去拉起一个新 IDE —— 见到这个报错,先去看 IDE 是否开着、端口是否变了。

设置页面(GUI)

本包是 Host + Client 双语 bundle:Host 半边持有带 volatile 字段的 Config,Client 半边把这些字段渲染成表单,并注册进两个官方槽位:

入口 槽位 路径
设置 → IntelliJ MCP(推荐) settings.section 打开设置面板,左栏最后一个导航项就是 IntelliJ MCP(排序 25,在内置插件之后)
侧边栏「插件」面板 plugins.row.config 侧栏「插件」→ 点开 dsh-intellij-mcp 包卡片 → 在 intellij-mcp 这一行点「配置」按钮(aria-label="配置 dsh-intellij-mcp")

两个入口共用同一份表单与同一份暂存状态,都是五个字段。

注意:不是设置里的「内置插件」页。那一页是 dsh-client-ui-settings-plugin-inventory(只读清单),既不派发 settings.section 也不派发 plugins.row.config,因此不会出现配置控件。

只能在 127.0.0.1 页面上编辑(DSH 的硬约束)

dsh-client-ui-settings 按页面来源选择设置的持久化后端:

const persistence = ctx.remote.$host.isLoopback ? "host" : "memory";

而 isLoopbackHostname 只认 localhost、[::1] 和字面 127.x.x.x——任何 DNS 名都不算。所以经反向代理/内网域名访问时(例如 dsh.ceastar.cn,实际由 dsh-pocket 代理到 3080),设置会被降级为进程内 memory 模式:describe 镜像永不加载,表单因此只读。

这是 DSH 的故意安全设计(源码注释 non-loopback pages may remain process-local),不是本插件的缺陷;本插件做的事是无条件注册入口,保证远端页面上导航项照样出现,只是表单里显示一段说明而不是字段。

要在远端机器上改这些设置,请走 loopback:

  • 直接在该机器上打开 dsh web 打印的 http://127.0.0.1:3080/?token=...;或
  • 用 SSH 端口转发:ssh -N -L 3080:127.0.0.1:3080 <机器>,然后本地浏览器访问 http://127.0.0.1:3080/?token=...。
字段 默认值 说明
IDE 启动器 command idea 以 idea stdioMcpServer 方式运行;idea 不在 PATH 时填绝对路径(JetBrains Toolbox 用户通常是 ~/.local/share/JetBrains/Toolbox/scripts/idea)
项目路径 projectPath 空 作为 IJ_MCP_SERVER_PROJECT_PATH 传入;留空则由 IDE 按调用自行解析工程
MCP 端口 port 64342 作为 IJ_MCP_SERVER_PORT 传入,须与 IDE 一致
工具调用超时 toolCallTimeoutMs 120000 build_project / run_inspection_kts / 全项目检索常超 60s 默认值
重连次数 maxReconnectAttempts 120 退避 500ms 起翻倍至 30s,约覆盖一小时;期间打开 IDE 即可自动连上
serverName intellij 非 volatile:工具名前缀来源,须在活跃实例间唯一
failOnStartupError false 非 volatile:IDE 没开时 DSH 仍能干净启动,由 reconnect 重试

保存即生效:volatile 字段的写入不会重挂插件,Host 半边的 loader/volatile-update 监听会主动断开并按新值重建 MCP 子连接,所以在表单里改完端口/项目路径点保存后,不必重启 Host。

写入落点:当前 profile 的 cordis.patch.yml(作为该行的覆盖层);把字段改回默认值保存即等同于删除该覆盖。

测试连接

表单底部有一个 测试连接 按钮,用来回答「我这样配到底通不通」。它不走 DSH 的设置通道,而是 POST 到 Host 半边注册的一条 exact 路由:

POST /plugins/dsh-intellij-mcp/test

Host 会按表单里的当前值(读的是 volatile ref 的活值,不是磁盘上的旧值)真的拉起一次 idea stdioMcpServer,用 NDJSON 帧完成 MCP 握手(initialize → notifications/initialized → tools/list),然后 SIGKILL 掉这个探测进程。响应是一份 JSON,前端把它渲染成几行结果:

结果 含义
绿字「连接成功,IDE 的 MCP 服务器已完成握手。」 握手成功,同时列出 protocol 2024-11-05、IntelliJ IDEA MCP Server 2026.2.1 和 Tools: execute_tool
红字「未连接」+ 具体原因 见下表
额外一行「智能体侧的对应工具也已注册。」 Host 侧 ctx.tools.get("mcp__intellij__execute_tool") 命中,说明 DSH 这一侧也把工具挂上了
额外一行「本插件自己的工具此刻未注册…在本页保存一次即可重连。」 探测成功,但插件当前的连接没挂上工具(改过配置还没保存、或重连已放弃);保存一次即可

失败原因(ok: false 的 reason)逐条对应:

reason 含义与常见成因
timeout 20 秒内没完成握手:IDE 没开、端口不是 IDE 内置 MCP 的端口(查法 ss -lntp | grep 64342)、或启动器路径不对
spawn-failed 启动器根本起不来(路径不存在 / 没有执行权限)
exited 启动器在握手完成前退出——典型的是错端口,stderr 会带 SSEClientException: Connection refused
handshake-failed 连上了但 initialize 被拒
tools-list-failed initialize 成功但 tools/list 没答
probe-error 探测本身抛异常

结果里还会带回 stderr 尾部(最多 600 字符),直接显示在结果区,便于对照上面那张表。

这条路由和 DSH 自己的 /api 不同:它不受客户端 isLoopback 限制,但远端页面上的表单本身是只读的(见上一节),所以「测试连接」按钮只在 loopback 页面上真正可用。远端页面能看到入口与说明,但不能测试。

为什么不直接给 @deepseek-ai/dsh-mcp-client 那一行做设置页:它的 Config 是普通 union、没有任何 volatile 字段,因此永远不会出现在设置表单里。本包因此自己持有 volatile Config,并在内部用 ctx.plugin(mcpClient, 解析后的配置) 复用它全部连接与工具桥接逻辑。

安装 / 卸载

本包已发布到 npm:https://www.npmjs.com/package/dsh-intellij-mcp

直接从 registry 安装:

plugin_manager action=install_bundle target=dsh-intellij-mcp

装完后 DSH 会在当前 profile 的 cordis.patch.yml 自动插入 intellij-mcp 行,无需手工配置。国内镜像(npmmirror)已同步该版本,pnpm 默认走镜像即可正常拉到。

首次使用请在设置页填入你自己的 IDE 启动器路径与项目路径 —— 包内默认值是可移植的裸 idea 与空项目路径,不包含任何机器专属路径:

  • idea 不在 PATH 时,填绝对路径(JetBrains Toolbox 用户通常为 ~/.local/share/JetBrains/Toolbox/scripts/idea)。
  • 项目路径留空亦可,但 IDE 需已打开目标项目。

必须检查返回值的 application 与 warnings:

application 含义
applied 已生效(新行已在 Loader 中)
restart-required 尚未生效:见下方绕法
overridden 被更高优先级层覆盖

restart-required 的绕法:该状态表示包已在 profile dependencies 里,installBundle 直接短路返回、不调用 reload(),于是新补丁没被推进 Loader(表现为 Config.listConfigs(entry="include:intellij-mcp") 仍返回旧的 mcp-client 行,或直接报 unknown entry id)。用 set_bundle 往返一次即可强制 reload:

plugin_manager action=set_bundle target=dsh-intellij-mcp enabled=false   # changed:true, application:applied
plugin_manager action=set_bundle target=dsh-intellij-mcp enabled=true    # changed:true, application:applied

卸载用 action=remove_bundle。

替换 JS 实现(lib/*.js)需要重启 Host 才会加载新模块代;只改 cordis.patch.yml 可以热加载。

本地开发(link: 安装)

在包目录里改代码、想立刻生效时才需要这条路径:

plugin_manager action=install_bundle target=dsh-intellij-mcp@link:<你的包目录绝对路径>

裸路径形式(target=<你的包目录绝对路径>)会报 ManagementFailure: ambiguous-install —— pnpm 若判定依赖已是最新就不产生变更,而裸路径又匹配不上任何包名,于是 installed.length !== 1 直接抛错。

link: 安装下 Node 按 realpath 解析,裸包名会在包目录下失去解析基准,因此本地开发时包内需要自带 node_modules/@deepseek-ai/,用 symlink 指向 dsh 安装目录里的同款包:

node_modules/@deepseek-ai/dsh-mcp-client -> <dsh>/node_modules/@deepseek-ai/dsh-mcp-client
node_modules/@deepseek-ai/schemastery    -> <dsh>/node_modules/@deepseek-ai/schemastery

(这两个 symlink 已在 .gitignore/files 之外,不会进 npm 包;从 registry 正常安装时 peer 依赖由 npm 自动落地,无需它们。)重新安装或换 dsh 版本后若报 Cannot find package '@deepseek-ai/...',按上面的形状重建这两个链接即可。

用法示例

execute_tool 的参数语法是 <工具名> --<参数名> <值>(对象/数组参数传 JSON 字符串):

get_project_modules
read_file --file_path <你的项目路径>/server/pom.xml
search_text --q TODO --directoryPath <你的项目路径>/server
get_file_problems --file_path <abs-path>
git_status

写错语法时的报错原文很明确:

Invalid argument format: 'file_path=...'. Expected '--paramName value' format.
For object/array parameters pass a JSON value, e.g. --findings '[{...}]'.

想拿到完整工具清单:随便传一个不存在的工具名,报错里会把全部可用工具名列出来(约 50 个:apply_patch、build_project、rename_refactoring、search_symbol、get_symbol_info、run_inspection_kts、execute_terminal_command、xdebug_*、数据库类 execute_sql_query / list_schema_objects 等)。

让智能体知道这些能力

IDEA 的 MCP 服务器不在 initialize 里宣告 instructions(实测响应只有 protocolVersion / capabilities / serverInfo),所以 dsh-mcp-client 自动发布的 mcp:intellij 提示词段对本服务器恒为空——光连上,模型并不知道 execute_tool 背后有哪些工具、参数怎么写。

本插件因此在 Host 半边自己发布一个提示词段:

ctx.inject(["systemPrompt"], (child) => {
  child.effect(() => child.systemPrompt.section({
    name: "intellij-mcp:tools",
    order: child.systemPrompt.getSectionOrder("MCP_SERVERS"),
    interpolate: false,
    text: TOOL_GUIDE,
  }), "intellij-mcp: tool guide");
});

段名固定为 intellij-mcp:tools(刻意与 mcp:<serverName> 区分开),排序与其他 MCP server 段并列,text 就是 lib/index.js 里的 TOOL_GUIDE 常量。它包含:

  1. 唯一工具与调用语法:mcp__intellij__execute_tool,参数 command 形如 <工具名> --<参数名> <值>(对象/数组传 JSON 字符串),并点明写错时的报错原文;

  2. 代理语义:idea stdioMcpServer 只是连接「已经在运行的 IDE」的 stdio↔SSE 代理,拒绝连接 = IDE 没开或端口不对;

  3. 一条明确的优先级策略:

    凡是代码类任务(找符号、读文件、查引用、重构、重命名、格式化、检查、构建)先试 IDE——它能跨整个工程解析真实语义,这是文本搜索做不到的;只有当 IDE 调用失败(IDE 未开、工程未打开、工具报错)或查询本质上是文本(日志、配置值、生成产物、工程外文件)时才退回 bash / rg。

  4. 按用途分组的工具清单:读取与检索、编辑与重构、构建/检查/运行、调试器(xdebug_*)、数据库。

改完 lib/index.js 记得重启 Host(systemctl --user restart dsh-web.service)——Host 半边是 ESM,热重载不会重新 import。

故障排查

现象 原因 处理
SSEClientException: Connection refused IDE 没开,或端口不是 IDE 当前监听的端口 打开 IDE / 用 ss -lntp 核对端口并在设置页改
工具调用返回 File ... doesn't exist or can't be opened 路径错(execute_tool 不走 shell,相对路径按 IDE 项目根解析,建议一律传绝对路径) 传绝对路径
工具不见了 超过 maxReconnectAttempts 后放弃,工具被注销 在设置页点保存(重建连接)或重启 Host
模型不知道 IDEA 有哪些工具、也不优先用 IDE 你用的是 1.0.4 及以前(提示词段 intellij-mcp:tools 是 1.0.5 才发布的;IDEA 自己的 initialize 不宣告 instructions,所以没有这个段模型就是盲的) 升级到 1.0.5+ 后重启 Host
设置里没有 IntelliJ MCP 导航项 ① 你用的是旧版(1.0.1 及以前没有 settings.section 注册);② 或 Host 没有 serve 命名空间 intellij-mcp(行未加载 / 无 volatile 字段) ① 升级到 1.0.3+ 后重启 Host;② 确认 bundle 已 applied 且该行处于启用状态(Config.listConfigs(entry="include:intellij-mcp") 应返回 name: dsh-intellij-mcp)
表单底部没有 测试连接 按钮 你用的是 1.0.3 及以前(该按钮 1.0.4 才有) 升级到 1.0.4+ 后重启 Host(Host 半边是 ESM,热重载不会重新 import)
导航项能看见,但点进去只有一段「只能在运行 DSH 的那台机器上编辑…」 你是经非 loopback 地址访问(任何 DNS 名都算,例如经反代/pocket 代理的 dsh.ceastar.cn),DSH 把设置降级成进程内 memory 模式 改用 http://127.0.0.1:3080/?token=...,或 SSH 端口转发到本机后再访问。1.0.2 及以前在同样场景下连导航项都不出现
设置 →「内置插件」页里找不到配置控件 那一页是只读清单,既不派发 settings.section 也不派发 plugins.row.config 改用设置左栏的 IntelliJ MCP,或侧边栏「插件」面板里该行的「配置」按钮
「测试连接」报红字「未连接」并附原因 探测真的去连了 IDE(见上一节的原因表),说明当前配置在这一次探测里不通 按原因处理:开 IDE / 核对端口 / 修正启动器路径;结果区里的 stderr 尾部通常直接给出根因
「测试连接」返回 HTTP 404 或 405 Host 半边的 exact 路由没注册——改了 lib/index.js 后只做了热重载而没有真实重启 systemctl --user restart dsh-web.service。lib/*.js 是 ESM,Host 侧有模块实例缓存,set_bundle 往返只重载 cordis.patch.yml,不会重新 import 代码
日志出现 failed generation could not confirm transport closure 上一代连接未确认关闭,为防重复拉起 IDE 进程而停止重连 重启 Host 后重试

目录结构

dsh-intellij-mcp/
├── package.json          # bundle 清单:dsh.bundle.patch + dsh.client + exports["./client"]
├── cordis.patch.yml      # 插入本包自己的 Host 行 intellij-mcp(name: dsh-intellij-mcp)
├── lib/index.js          # Host 半边:volatile Config + 管理 mcp-client 子连接 + 测试连接路由 + 工具能力提示词段
├── lib/client.js         # Client 半边:把 volatile 字段渲染成设置页表单 + 测试连接按钮
├── locale/{en,zh}.json   # 面板显示用标题与描述
├── LICENSE
└── README.md

许可

MIT