opencode-idea 0.0.16 → 0.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +164 -61
- package/package.json +5 -1
- package/skills/idea-standalone/SKILL.md +37 -89
- package/skills/idea-standalone/assets/AGENTS.template.md +66 -0
- package/src/ide-guidance.js +61 -84
- package/src/ide-guidance.template.md +71 -0
- package/src/mcp/enhance.js +59 -0
- package/src/mcp/idea-tool-defaults.js +155 -0
- package/src/mcp/idea.js +2 -2
- package/src/plugin.js +205 -47
- package/src/skill/loader.js +45 -8
- package/src/template.js +94 -0
- package/skills/idea-run-config/SKILL.md +0 -118
package/src/ide-guidance.js
CHANGED
|
@@ -1,91 +1,63 @@
|
|
|
1
1
|
// IDEA MCP routing guidance injected into the model's system prompt.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
3
|
+
// The TEXT lives in `ide-guidance.template.md`, not here. It is a prompt document
|
|
4
|
+
// a human has to review as a document — with the conditionals visible as
|
|
5
|
+
// `{{#if …}}` around the lines they control — and building it by concatenating
|
|
6
|
+
// arrays made the result impossible to read without running the code. The
|
|
7
|
+
// template renders byte-for-byte to what the model receives; see `src/template.js`
|
|
8
|
+
// for why the renderer is 60 lines here rather than an engine dependency.
|
|
9
|
+
//
|
|
10
|
+
// Two rules for the document itself:
|
|
4
11
|
// 1. Per tool: what it does, its parameters, and the positive flow for using
|
|
5
12
|
// it. No caveats or edge cases, and nothing about languages or frameworks —
|
|
6
13
|
// the project's own setup decides that.
|
|
7
|
-
// 2.
|
|
8
|
-
//
|
|
14
|
+
// 2. Only IDE-only capability is routed here; reading, writing and searching
|
|
15
|
+
// stay on the native tools.
|
|
16
|
+
//
|
|
17
|
+
// The IDE MCP server is registered with `codemode: false`, so every idea_* tool is
|
|
18
|
+
// exposed directly on the provider tool list with its full schema; the document
|
|
19
|
+
// says which tool to use for which operation, plus the parameters and result
|
|
20
|
+
// shapes that are not obvious from the schema alone.
|
|
9
21
|
//
|
|
10
|
-
// The
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
22
|
+
// The `{{#if can…}}` blocks gate the companion plugin's tools (`mcp/enhance.js`),
|
|
23
|
+
// which only exist when the user installed it. Nothing in the document says so:
|
|
24
|
+
// which plugin a tool came from is this plugin's plumbing, not the model's
|
|
25
|
+
// business — it either sees the tool or it does not.
|
|
26
|
+
|
|
27
|
+
import { readFileSync } from 'node:fs';
|
|
28
|
+
|
|
29
|
+
import {
|
|
30
|
+
CREATE_SHELL_RUN_CONFIGURATION_TOOL,
|
|
31
|
+
LIST_RUNNING_CONFIGURATIONS_TOOL,
|
|
32
|
+
RERUN_RUN_CONFIGURATION_TOOL,
|
|
33
|
+
STOP_RUN_CONFIGURATION_TOOL,
|
|
34
|
+
} from './mcp/enhance.js';
|
|
35
|
+
import { renderTemplate } from './template.js';
|
|
36
|
+
|
|
37
|
+
/** The prompt document, next to this module so it ships with it. */
|
|
38
|
+
const TEMPLATE_PATH = new URL('ide-guidance.template.md', import.meta.url);
|
|
39
|
+
|
|
40
|
+
/** Read once: the file is fixed for the life of the process, and this runs per request. */
|
|
41
|
+
const TEMPLATE = readFileSync(TEMPLATE_PATH, 'utf8');
|
|
14
42
|
|
|
15
|
-
/**
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
`- \`idea_search_file\`:不要用 \`glob\`。`,
|
|
34
|
-
`- \`idea_search_symbol\`:按名字找符号的声明位置;要看签名 / 文档接着用 \`idea_get_symbol_info\`。`,
|
|
35
|
-
``,
|
|
36
|
-
`### 阅读`,
|
|
37
|
-
`- \`idea_read_file\`:不要用 \`read\`;支持 \`<jar>!/<entry>\` 与 \`jrt://\` 形式的包内读取。`,
|
|
38
|
-
`- \`idea_list_directory_tree\`:不要用 \`ls\` / \`find\`;\`maxDepth\` 控深度。`,
|
|
39
|
-
`- \`idea_get_symbol_info\`:一个符号的签名 / 文档 / 声明位置,\`filePath\` + \`line\` + \`column\`。`,
|
|
40
|
-
`- \`idea_get_all_open_file_paths\`:用户在 IDE 里打开了哪些文件(含当前激活的那个)。`,
|
|
41
|
-
`- \`idea_open_file_in_editor\`:把文件在 IDE 里打开并激活,给用户看。`,
|
|
42
|
-
``,
|
|
43
|
-
`### 写入`,
|
|
44
|
-
`- \`idea_apply_patch\`:不要用 \`edit\` / \`patch\`;只接受项目内路径。`,
|
|
45
|
-
`- \`idea_create_new_file\`:不要用 \`write\`;\`overwrite\` 控制同名覆盖。`,
|
|
46
|
-
`- \`idea_rename_refactoring\`:按符号改名,项目内的引用一并更新。`,
|
|
47
|
-
`- \`idea_reformat_file\`:按 IDE 的代码风格重排。`,
|
|
48
|
-
``,
|
|
49
|
-
`### 校验`,
|
|
50
|
-
`- \`idea_lint_files\`:批量检查,\`files\` 传数组;只列有问题的文件。`,
|
|
51
|
-
`- \`idea_get_file_problems\`:单文件检查,\`filePath\`;结果在 \`errors\`。`,
|
|
52
|
-
`- \`idea_build_project\`:构建校验,\`filesToRebuild\` 可只构建指定文件;有问题返回在 \`problems\`,没返回即没问题。`,
|
|
53
|
-
``,
|
|
54
|
-
`### 分析`,
|
|
55
|
-
`- \`idea_analyze_calls\`:调用关系,\`symbolFqn\` 传符号名,\`analysisKind\` 取 \`INCOMING_CALLS\` / \`OUTGOING_CALLS\`;\`depth\` 控层数,\`childOffset\` 翻页。`,
|
|
56
|
-
`- \`idea_get_project_modules\`:项目模块及类型。`,
|
|
57
|
-
`- \`idea_get_project_dependencies\`:项目直接声明的依赖。`,
|
|
58
|
-
``,
|
|
59
|
-
`### 运行配置`,
|
|
60
|
-
`- \`idea_get_run_configurations\`:读项目级 \`.run/*.run.xml\`(增删改直接改文件);传 \`filePath\` 可列该文件的运行入口。`,
|
|
61
|
-
`- \`idea_execute_run_configuration\`:跑已有配置;Shell Script 要 \`EXECUTE_IN_TERMINAL: false\`;覆盖参数(\`programArguments\` / \`workingDirectory\` / \`envs\`)需配置支持 \`supportsDynamicLaunchOverrides\`。`,
|
|
62
|
-
``,
|
|
63
|
-
`### 终端`,
|
|
64
|
-
`- 白名单制:命令用于本项目自身的构建 / 测试 / 运行,且首个可执行文件命中下表,才用 \`idea_execute_terminal_command\`;其余一律走原生 \`shell\`。`,
|
|
65
|
-
`- 白名单(按本项目类型取对应一行) ——`,
|
|
66
|
-
` - Node / 前端(\`package.json\`):\`node\` / \`npm\` / \`npx\` / \`pnpm\` / \`yarn\` / \`vite\` / \`webpack\` / \`esbuild\` / \`tsc\` / \`jest\` / \`vitest\`。`,
|
|
67
|
-
` - Java(\`pom.xml\` / \`build.gradle\`):\`java\` / \`javac\` / \`mvn\` / \`mvnw\` / \`gradle\` / \`gradlew\`。`,
|
|
68
|
-
` - Python(\`pyproject.toml\` / \`requirements.txt\`):\`python\` / \`python3\` / \`pip\` / \`pip3\` / \`uv\` / \`poetry\` / \`pytest\`。`,
|
|
69
|
-
` - Go(\`go.mod\`):\`go\` / \`gofmt\` / \`golangci-lint\`。`,
|
|
70
|
-
` - \`executeInShell: true\`:在用户真实 shell 里执行,继承 IDE 配好的运行时环境;原生 shell 可能用错版本。`,
|
|
71
|
-
` - 按预期耗时传 \`timeout\`(毫秒),长任务给足;必要时传 \`maxLinesCount\` 控制返回行数。`,
|
|
72
|
-
` - 连续命令传 \`reuseExistingTerminalWindow: true\`;结果带 \`command_exit_code\` 与 \`command_output\`,\`cd\` 不跨调用保留,多步用 \`&&\`。`,
|
|
73
|
-
``,
|
|
74
|
-
`### 数据库 (数据源由用户在 IDE 里配置:不要自己建/改,不要向用户索取数据库凭据)`,
|
|
75
|
-
`- \`idea_list_database_connections\`:先看有没有现成数据源;每条给 \`id\` / \`dbms\` / \`jdbcUrl\` / \`readOnly\` / \`isDDL\`。`,
|
|
76
|
-
`- \`idea_test_database_connection\`(\`id\`):\`hasProblems\` 为 \`NO\` 才算通,另带 DBMS / 驱动版本与 ping。`,
|
|
77
|
-
`- \`idea_list_database_schemas\`(\`connectionId\`):MySQL 下 \`databaseName\` 为空、库名在 \`schemaName\`;带 \`isIntrospected\`。`,
|
|
78
|
-
`- \`idea_list_schema_object_kinds\`(\`connectionId\`):先看有哪些类型可查(\`table\` / \`view\` / \`routine\` / \`scheduled-event\`)。`,
|
|
79
|
-
`- \`idea_list_schema_objects\`(\`connectionId\` + \`databaseName\` + \`schemaName\` + \`kind\`):列该 schema 下的对象。`,
|
|
80
|
-
`- \`idea_introspect_schema\`(\`connectionId\` + \`databaseName\` + \`schemaName\`):拉元数据,返回该 schema 的 \`isIntrospected\`。`,
|
|
81
|
-
`- \`idea_get_database_object_description\`(\`kind\` + \`objectName\`):层级文本,含列类型 / 列注释 / 索引 / 主键。`,
|
|
82
|
-
`- \`idea_execute_sql_query\`:跑 SQL;结果在 \`text\`(CSV)与 \`resultSetId\`,一次 10 行,后续用 \`fetch_query_result\` 翻页。`,
|
|
83
|
-
`- \`idea_fetch_query_result\`(\`resultSetId\` + \`offset\`):按页继续取结果,10 行一页。`,
|
|
84
|
-
`- \`idea_preview_table_data\`(\`tableName\` + \`maxRowCount\`):直接回 CSV。`,
|
|
85
|
-
`- \`idea_list_recent_sql_queries\`(\`connectionId\`):最近(含正在跑)的查询,带 \`sessionId\` / \`state\` / \`status\`。`,
|
|
86
|
-
`- \`idea_cancel_sql_query\`(\`sessionId\`):取消正在跑的查询,成功后该条 \`status\` 变 \`CANCELLED\`。`,
|
|
87
|
-
`- 没有数据源时:若目标库在 IDEA Database Tools 支持范围内(PostgreSQL / MySQL / MariaDB / Oracle / SQL Server / SQLite / DB2 / H2 / Derby / Sybase / Vertica / Greenplum / ClickHouse / CockroachDB / Snowflake / BigQuery / Redshift / MongoDB / Redis / Cassandra / Couchbase / Hive / DynamoDB / Exasol 等,或任何提供 JDBC 驱动的库),先建议用户在 IDEA 里配好数据源,再回来用工具操作。`,
|
|
88
|
-
].join('\n');
|
|
43
|
+
/**
|
|
44
|
+
* Build the guidance text for one project path.
|
|
45
|
+
*
|
|
46
|
+
* @param {string} projectPath
|
|
47
|
+
* @param {{ runLifecycleTools?: readonly string[] }} [options] Which of the
|
|
48
|
+
* companion plugin's tools this request carries (`servingRunLifecycleTools`);
|
|
49
|
+
* omit for a stock IDE.
|
|
50
|
+
* @returns {string}
|
|
51
|
+
*/
|
|
52
|
+
export function buildIdeGuidance(projectPath, options = {}) {
|
|
53
|
+
const present = options.runLifecycleTools ?? [];
|
|
54
|
+
return renderTemplate(TEMPLATE, {
|
|
55
|
+
projectPath,
|
|
56
|
+
canCreateRunConfiguration: present.includes(CREATE_SHELL_RUN_CONFIGURATION_TOOL),
|
|
57
|
+
canListRunningConfigurations: present.includes(LIST_RUNNING_CONFIGURATIONS_TOOL),
|
|
58
|
+
canStopRunConfiguration: present.includes(STOP_RUN_CONFIGURATION_TOOL),
|
|
59
|
+
canRerunRunConfiguration: present.includes(RERUN_RUN_CONFIGURATION_TOOL),
|
|
60
|
+
});
|
|
89
61
|
}
|
|
90
62
|
|
|
91
63
|
export const MCP_RECOVERY_GUIDANCE = [
|
|
@@ -113,20 +85,25 @@ export const MCP_RECOVERY_GUIDANCE_MARKED = [
|
|
|
113
85
|
*/
|
|
114
86
|
export const MCP_RECOVERY_GUIDANCE_RECONNECTING = [
|
|
115
87
|
'### IDEA MCP failure recovery',
|
|
116
|
-
'No idea_* tool is in this request: the IDE connection dropped and the plugin is reconnecting in the background. Do NOT call any idea_* tool now — it will fail with `No tool named "..." is currently available` — and do not fall back to shell/read/write for in-project work. Continue with what you can, or tell the user the IDE is reconnecting; if idea_* tools are still missing on a later message, ask the user to run /open-in-idea.',
|
|
88
|
+
'No idea_* tool is in this request: the IDE connection dropped and the plugin is reconnecting in the background. Do NOT call any idea_* tool now — it will fail with `No tool named "..." is currently available` — and do not go looking for them through Code Mode either: idea_* tools are only ever listed as native tools, so a search finds nothing. Do not fall back to shell/read/write for in-project work. Continue with what you can, or tell the user the IDE is reconnecting; if idea_* tools are still missing on a later message, ask the user to run /open-in-idea.',
|
|
117
89
|
].join('\n');
|
|
118
90
|
|
|
119
91
|
/**
|
|
120
92
|
* Push the guidance onto a session `system` array. Called for every primary
|
|
121
93
|
* model request, so it stays idempotent per request.
|
|
122
94
|
*
|
|
95
|
+
* The text depends on which run-lifecycle tools this request carries, so the
|
|
96
|
+
* idempotence check compares the built text rather than assuming one variant.
|
|
97
|
+
*
|
|
123
98
|
* @param {unknown} system
|
|
124
99
|
* @param {string} projectPath
|
|
100
|
+
* @param {{ runLifecycleTools?: readonly string[] }} [options] Run-lifecycle
|
|
101
|
+
* tools present in this request's tool snapshot.
|
|
125
102
|
*/
|
|
126
|
-
export function appendIdeGuidance(system, projectPath) {
|
|
103
|
+
export function appendIdeGuidance(system, projectPath, options = {}) {
|
|
127
104
|
if (!Array.isArray(system)) return;
|
|
128
105
|
if (!projectPath) return;
|
|
129
|
-
const text = buildIdeGuidance(projectPath);
|
|
106
|
+
const text = buildIdeGuidance(projectPath, options);
|
|
130
107
|
if (system.some((part) => part?.text === text)) return;
|
|
131
108
|
system.push({ type: 'text', text });
|
|
132
109
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# IDEA MCP:只有非 IDE 不可的能力才走 IDEA
|
|
2
|
+
|
|
3
|
+
项目路径: `{{projectPath}}`
|
|
4
|
+
|
|
5
|
+
## 总则
|
|
6
|
+
|
|
7
|
+
- 原生工具(当前工具列表里直接列出的那些)**直接调用**;不需要、也不应该用 Code Mode 的 `search` 去找。
|
|
8
|
+
- `idea_*` 就是原生工具:直接调用(参数按各工具 schema 传),不要包进 `execute`;它不在 Code Mode 的工具目录里,`search` 搜不到。
|
|
9
|
+
- **只有 IDE 才有的能力才用 `idea_*`**:符号级检索、项目模型、检查与构建、重构与格式化、run configuration。
|
|
10
|
+
- **读写与搜索用原生工具**(`read` / `write` / `edit` / `grep` / `glob`):不要为读一个文件、写一个文件、搜一段文本去绕 `idea_*`。
|
|
11
|
+
- 唯一例外:`idea_read_file` —— **只在要读包内文件时用它**(`<jar>!/<entry>` 形式的依赖 jar 内文件、`jrt://` 形式的 JDK 镜像),那是原生 `read` 做不到的;普通文件一律用 `read`。
|
|
12
|
+
- `idea_*` 只作用于 `{{projectPath}}`(IDE 里打开的那个项目)。
|
|
13
|
+
- 工具列表里看不到 `idea_*` 就是没接上:提示用户执行 `/open-in-idea`,不要重试,也不要去别处找。
|
|
14
|
+
|
|
15
|
+
## 工具补充(每条只写补充说明;工具定义以自身 schema 为准)
|
|
16
|
+
|
|
17
|
+
### 符号(只有 IDE 的语义索引能给)
|
|
18
|
+
|
|
19
|
+
- `idea_search_symbol`:按名字找符号的声明位置;要看签名 / 文档接着用 `idea_get_symbol_info`。
|
|
20
|
+
- `idea_get_symbol_info`:一个符号的签名 / 文档 / 声明位置,`filePath` + `line` + `column`。
|
|
21
|
+
- `idea_analyze_calls`:调用关系,`symbolFqn` 传符号名,`analysisKind` 取 `INCOMING_CALLS` / `OUTGOING_CALLS`;`depth` 控层数,`childOffset` 翻页。
|
|
22
|
+
|
|
23
|
+
### 项目与编辑器
|
|
24
|
+
|
|
25
|
+
- `idea_list_directory_tree`:项目视角的目录树(尊重内容根与排除项),`maxDepth` 控深度。
|
|
26
|
+
- `idea_get_project_modules`:项目模块及类型。
|
|
27
|
+
- `idea_get_project_dependencies`:项目直接声明的依赖。
|
|
28
|
+
- `idea_get_all_open_file_paths`:用户在 IDE 里打开了哪些文件(含当前激活的那个)。
|
|
29
|
+
- `idea_open_file_in_editor`:把文件在 IDE 里打开并激活,给用户看。
|
|
30
|
+
|
|
31
|
+
### 重构与格式化
|
|
32
|
+
|
|
33
|
+
- `idea_rename_refactoring`:按符号改名,项目内的引用一并更新。
|
|
34
|
+
- `idea_reformat_file`:按 IDE 的代码风格重排。
|
|
35
|
+
|
|
36
|
+
### 校验
|
|
37
|
+
|
|
38
|
+
- `idea_lint_files`:批量检查,`files` 传数组;只列有问题的文件。
|
|
39
|
+
- `idea_get_file_problems`:单文件检查,`filePath`;结果在 `errors`。
|
|
40
|
+
- `idea_build_project`:构建校验,`filesToRebuild` 可只构建指定文件;有问题返回在 `problems`,没返回即没问题。
|
|
41
|
+
|
|
42
|
+
### 运行配置
|
|
43
|
+
|
|
44
|
+
- `idea_get_run_configurations`:项目里有哪些运行配置;传 `filePath` 可列该文件的运行入口。
|
|
45
|
+
- `idea_execute_run_configuration`:跑已有配置;Shell Script 要 `EXECUTE_IN_TERMINAL: false`;覆盖参数(`programArguments` / `workingDirectory` / `envs`)需配置支持 `supportsDynamicLaunchOverrides`。
|
|
46
|
+
{{#if canCreateRunConfiguration}}
|
|
47
|
+
- `idea_create_shell_run_configuration`(`configurationName` + `scriptPath` / `scriptText` / `cloneFrom` 三选一):新建项目级 Shell 配置(写 `.run/<name>.run.xml`);**重操作** —— 只给固定要跑、反复要用的流程建,**且建之前必须先取得用户同意**;只支持 Shell 类型。
|
|
48
|
+
{{/if}}
|
|
49
|
+
{{#if canListRunningConfigurations}}
|
|
50
|
+
- `idea_list_running_configurations`:当前真正在跑的配置(几个实例 / 各自 pid / 退出码);停 / 重启前后都先用它看状态。
|
|
51
|
+
{{/if}}
|
|
52
|
+
{{#if canStopRunConfiguration}}
|
|
53
|
+
- `idea_stop_run_configuration`(`configurationName`):停掉该配置的全部实例并等它们退出;没在跑也返回成功。
|
|
54
|
+
{{/if}}
|
|
55
|
+
{{#if canRerunRunConfiguration}}
|
|
56
|
+
- `idea_rerun_run_configuration`(`configurationName`):改完代码重启无热重载的进程;在跑就重启、没在跑就启动,读 `started` 判断新进程是否真的起来了。不要用 `pkill` / `kill` / `shell` 代替。
|
|
57
|
+
{{/if}}
|
|
58
|
+
|
|
59
|
+
### 命令执行
|
|
60
|
+
|
|
61
|
+
- 需要项目环境的命令(依赖 IDE 里配好的 JDK / Node / Python 版本等):用 `idea_execute_run_configuration`,不要用原生 `shell`。
|
|
62
|
+
- run configuration 是**重资产**:一个项目通常只建几个(固定要跑的构建 / 装依赖 / dev server 之类),**不是随手一条命令就建一个**。
|
|
63
|
+
- 要新建:先确认它**反复要用、且属于项目固定流程**;确认后**先告诉用户要建什么、为什么,取得同意再动手**,别自己决定。
|
|
64
|
+
- **配置一律不由你写**:不要手写 `.run/*.run.xml`,也不要手搓启动命令。
|
|
65
|
+
- **要查有哪些配置,用 `idea_get_run_configurations`,不要翻 `.run/` 目录**:工具给的是 IDE 当前认的配置;翻目录只能看到项目级文件,存在 `.idea/` 里的那些看不到。
|
|
66
|
+
{{#if canCreateRunConfiguration}}
|
|
67
|
+
- **只有 Shell 类型**是例外:用 `idea_create_shell_run_configuration`。**其他类型请用户在 IDEA 里建。**
|
|
68
|
+
{{else}}
|
|
69
|
+
- 缺配置就请用户在 IDEA 里建。
|
|
70
|
+
{{/if}}
|
|
71
|
+
- 不需要项目环境的命令:用原生 `shell`。
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// MCP surface added by the companion IDEA plugin `idea-mcp-enhance`.
|
|
2
|
+
//
|
|
3
|
+
// Kept apart from `idea.js` on purpose. `idea.js` describes ONE thing: the MCP
|
|
4
|
+
// server IntelliJ itself bundles, which every supported IDE has. Everything in
|
|
5
|
+
// this file describes a SECOND, optional source of tools — a plugin the user may
|
|
6
|
+
// or may not have installed. Two different origins, two files:
|
|
7
|
+
//
|
|
8
|
+
// * native -> `idea.js` (always present; the IDE owns the catalog)
|
|
9
|
+
// * plugin -> this file (present only if the user installed the plugin)
|
|
10
|
+
//
|
|
11
|
+
// The companion plugin (https://github.com/syllr/idea-mcp-enhance) fills the gaps
|
|
12
|
+
// the bundled server leaves in run configuration handling: the bundled server can
|
|
13
|
+
// only START an existing configuration — it cannot create one, stop one, restart
|
|
14
|
+
// one, or say what is currently running.
|
|
15
|
+
//
|
|
16
|
+
// Consequence of the split, and the reason it matters: a stock IDE serves a
|
|
17
|
+
// SHORTER catalog than an enhanced one. Nothing here may be assumed — every use
|
|
18
|
+
// has to be gated on the tools really being in the request (`servingRunLifecycleTools`).
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The enhance plugin's tool names as OpenCode reports them.
|
|
22
|
+
*
|
|
23
|
+
* OpenCode namespaces an MCP server's tools with the server name, and this
|
|
24
|
+
* plugin registers the IDE server as `idea`, so the wire names are the enhance
|
|
25
|
+
* plugin's own tool names (`list_running_configurations`, ...) carrying an
|
|
26
|
+
* `idea_` prefix.
|
|
27
|
+
*
|
|
28
|
+
* Keep in sync with `RunLifecycleSchemas.kt` in the enhance repository — the
|
|
29
|
+
* names are its contract, and there is no runtime way to discover "which plugin
|
|
30
|
+
* provided this tool": MCP carries no such marker.
|
|
31
|
+
*/
|
|
32
|
+
export const CREATE_SHELL_RUN_CONFIGURATION_TOOL = 'idea_create_shell_run_configuration';
|
|
33
|
+
export const LIST_RUNNING_CONFIGURATIONS_TOOL = 'idea_list_running_configurations';
|
|
34
|
+
export const STOP_RUN_CONFIGURATION_TOOL = 'idea_stop_run_configuration';
|
|
35
|
+
export const RERUN_RUN_CONFIGURATION_TOOL = 'idea_rerun_run_configuration';
|
|
36
|
+
|
|
37
|
+
/** All of them, in the order the guidance lists them: create, see, stop, restart. */
|
|
38
|
+
export const RUN_LIFECYCLE_TOOLS = [
|
|
39
|
+
CREATE_SHELL_RUN_CONFIGURATION_TOOL,
|
|
40
|
+
LIST_RUNNING_CONFIGURATIONS_TOOL,
|
|
41
|
+
STOP_RUN_CONFIGURATION_TOOL,
|
|
42
|
+
RERUN_RUN_CONFIGURATION_TOOL,
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Which of the enhance plugin's tools this request actually carries.
|
|
47
|
+
*
|
|
48
|
+
* A subset rather than a boolean, because the enhance plugin is versioned
|
|
49
|
+
* independently: an older release can ship fewer of these tools, and guidance
|
|
50
|
+
* must never name one the model cannot call.
|
|
51
|
+
*
|
|
52
|
+
* @param {Record<string, unknown> | undefined} tools This request's tool record
|
|
53
|
+
* (`event.tools` from the `context` hook).
|
|
54
|
+
* @returns {string[]} The present ones, in {@link RUN_LIFECYCLE_TOOLS} order.
|
|
55
|
+
*/
|
|
56
|
+
export function servingRunLifecycleTools(tools) {
|
|
57
|
+
if (!tools || typeof tools !== 'object') return [];
|
|
58
|
+
return RUN_LIFECYCLE_TOOLS.filter((name) => name in tools);
|
|
59
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// Arguments this plugin OWNS on the IDE's own MCP tools.
|
|
2
|
+
//
|
|
3
|
+
// Three files, three different subjects — kept apart on purpose:
|
|
4
|
+
//
|
|
5
|
+
// * `idea.js` the IDE's bundled MCP server: endpoint, transport, probe
|
|
6
|
+
// * `idea-tool-defaults` (this file) that server's TOOL CATALOG, and the
|
|
7
|
+
// arguments on it that this plugin controls
|
|
8
|
+
// * `enhance.js` a companion plugin's tools, which may not be installed
|
|
9
|
+
//
|
|
10
|
+
// ## Why an argument can be owned at all
|
|
11
|
+
//
|
|
12
|
+
// `idea_build_project` is the only way a model can learn whether the code it just
|
|
13
|
+
// wrote compiles, and its verdict depends on file freshness the model cannot see:
|
|
14
|
+
// an INCREMENTAL build compares against the IDE's own view of the files and
|
|
15
|
+
// recompiles only what that view calls dirty. When the edit happened OUTSIDE the
|
|
16
|
+
// IDE, the view can still be clean and the build becomes a no-op that reports
|
|
17
|
+
// success — a false "it compiles", the worst possible answer for a model
|
|
18
|
+
// verifying its own edit.
|
|
19
|
+
//
|
|
20
|
+
// MEASURED (IntelliJ 2026.2.3, decompiled `AnalysisToolset` plus 6 live
|
|
21
|
+
// scenarios): `build_project` maps to
|
|
22
|
+
// `ProjectTaskManager.createAllModulesBuildTask(project, isIncrementalBuild = !rebuild)`,
|
|
23
|
+
// so `rebuild: true` is the switch that makes the answer independent of VFS
|
|
24
|
+
// freshness. All six scenarios (edit an existing file / add a new one, in any
|
|
25
|
+
// package, open in the editor or not) were detected with `rebuild: true`, and
|
|
26
|
+
// only intermittently without it.
|
|
27
|
+
//
|
|
28
|
+
// ## Two halves of the same decision
|
|
29
|
+
//
|
|
30
|
+
// An argument that is always overwritten must not also be advertised. Leaving it
|
|
31
|
+
// in the schema would let the model set it, reason about it, and be wrong, while
|
|
32
|
+
// the value it wrote is silently discarded. So [`FORCED_ARGUMENTS`] drives both
|
|
33
|
+
// halves from one table:
|
|
34
|
+
//
|
|
35
|
+
// * [hideForcedArguments] drops it from the published input schema
|
|
36
|
+
// * [applyIdeaToolDefaults] sets the value on every call
|
|
37
|
+
//
|
|
38
|
+
// ## On rewriting a tool's schema
|
|
39
|
+
//
|
|
40
|
+
// This is a departure from the earlier "never touch tool definitions" rule, and it
|
|
41
|
+
// is deliberately narrow: nothing here INVENTS or EXPLAINS. The IDE's own
|
|
42
|
+
// `build_project` description does not mention the argument at all — only its
|
|
43
|
+
// per-property description does — so removing the property leaves a schema that
|
|
44
|
+
// is still internally consistent and still entirely the IDE's. What a schema
|
|
45
|
+
// transform must never do is add advice; that belongs to the guidance text.
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Arguments this plugin sets on every call, and therefore must not advertise.
|
|
49
|
+
*
|
|
50
|
+
* @type {ReadonlyArray<{ tool: string, argument: string, value: unknown }>}
|
|
51
|
+
*/
|
|
52
|
+
export const FORCED_ARGUMENTS = [
|
|
53
|
+
{
|
|
54
|
+
tool: 'idea_build_project',
|
|
55
|
+
argument: 'rebuild',
|
|
56
|
+
value: true,
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
/** The IDE tool whose verdict must not depend on the IDE's view of file freshness. */
|
|
61
|
+
export const BUILD_PROJECT_TOOL = 'idea_build_project';
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Set every owned argument on one call.
|
|
65
|
+
*
|
|
66
|
+
* Mutates `input` in place and reports whether anything changed, so a caller can
|
|
67
|
+
* keep "the model asked for this" apart from "we forced it". Unknown tools and
|
|
68
|
+
* malformed drafts are left untouched — this function owns the tools it names and
|
|
69
|
+
* nothing else.
|
|
70
|
+
*
|
|
71
|
+
* Called from `execute.before`, which hands out a mutable draft. The runtime
|
|
72
|
+
* validates a tool call against the schema published for THAT request and only
|
|
73
|
+
* then runs the hook, so setting a property the schema no longer advertises is
|
|
74
|
+
* safe.
|
|
75
|
+
*
|
|
76
|
+
* @param {unknown} tool Effective tool name for this call (`event.tool`).
|
|
77
|
+
* @param {unknown} input Mutable argument draft for this call (`event.input`).
|
|
78
|
+
* @returns {boolean} Whether `input` was modified.
|
|
79
|
+
*/
|
|
80
|
+
export function applyIdeaToolDefaults(tool, input) {
|
|
81
|
+
if (typeof tool !== 'string') return false;
|
|
82
|
+
if (input === null || typeof input !== 'object') return false;
|
|
83
|
+
|
|
84
|
+
let changed = false;
|
|
85
|
+
for (const owned of FORCED_ARGUMENTS) {
|
|
86
|
+
if (owned.tool !== tool) continue;
|
|
87
|
+
if (input[owned.argument] === owned.value) continue;
|
|
88
|
+
input[owned.argument] = owned.value;
|
|
89
|
+
changed = true;
|
|
90
|
+
}
|
|
91
|
+
return changed;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Drop one property from a JSON schema, returning a new object.
|
|
96
|
+
*
|
|
97
|
+
* Returns the input unchanged (by reference) when there is nothing to remove, so
|
|
98
|
+
* callers can skip an otherwise pointless edit. Everything else — including
|
|
99
|
+
* `additionalProperties` — is copied through untouched: this function only takes
|
|
100
|
+
* something away, and must never become a place where schema semantics change.
|
|
101
|
+
*
|
|
102
|
+
* @param {unknown} schema
|
|
103
|
+
* @param {string} name
|
|
104
|
+
* @returns {unknown} The new schema, or `schema` itself when unchanged.
|
|
105
|
+
*/
|
|
106
|
+
export function removeSchemaProperty(schema, name) {
|
|
107
|
+
if (schema === null || typeof schema !== 'object') return schema;
|
|
108
|
+
const properties = schema.properties;
|
|
109
|
+
if (properties === null || typeof properties !== 'object') return schema;
|
|
110
|
+
if (!(name in properties)) return schema;
|
|
111
|
+
|
|
112
|
+
const nextProperties = { ...properties };
|
|
113
|
+
delete nextProperties[name];
|
|
114
|
+
|
|
115
|
+
const next = { ...schema, properties: nextProperties };
|
|
116
|
+
if (Array.isArray(schema.required)) {
|
|
117
|
+
const required = schema.required.filter((entry) => entry !== name);
|
|
118
|
+
// Drop an emptied `required` rather than leaving `[]` behind: the two mean
|
|
119
|
+
// the same thing on the wire, but a stale empty array reads like intent.
|
|
120
|
+
if (required.length > 0) next.required = required;
|
|
121
|
+
else delete next.required;
|
|
122
|
+
}
|
|
123
|
+
return next;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Remove every owned argument from the tools this plugin controls.
|
|
128
|
+
*
|
|
129
|
+
* Takes the `ctx.tool.transform` editor (`list` / `get` / `update`). Written
|
|
130
|
+
* against that surface rather than against OpenCode's types so it stays a pure
|
|
131
|
+
* function of what it is given — which is also what makes it testable without a
|
|
132
|
+
* runtime.
|
|
133
|
+
*
|
|
134
|
+
* Safe to run when the IDE is absent: `get` returns undefined for a tool that is
|
|
135
|
+
* not registered, and OpenCode replays the transform after every MCP catalog
|
|
136
|
+
* refresh, so the tools are adjusted once they appear.
|
|
137
|
+
*
|
|
138
|
+
* @param {{
|
|
139
|
+
* get: (id: string) => { input?: unknown } | undefined,
|
|
140
|
+
* update: (id: string, update: (tool: { input?: unknown }) => void) => void,
|
|
141
|
+
* }} editor
|
|
142
|
+
*/
|
|
143
|
+
export function hideForcedArguments(editor) {
|
|
144
|
+
for (const owned of FORCED_ARGUMENTS) {
|
|
145
|
+
const tool = editor.get(owned.tool);
|
|
146
|
+
if (!tool) continue;
|
|
147
|
+
const next = removeSchemaProperty(tool.input, owned.argument);
|
|
148
|
+
if (next === tool.input) continue;
|
|
149
|
+
// Assign the whole schema: the editor documents that schemas are replaced,
|
|
150
|
+
// not mutated in place.
|
|
151
|
+
editor.update(owned.tool, (draft) => {
|
|
152
|
+
draft.input = next;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
package/src/mcp/idea.js
CHANGED
|
@@ -122,7 +122,7 @@ export async function probePort(port, options = {}) {
|
|
|
122
122
|
params: {
|
|
123
123
|
protocolVersion: PROTOCOL_VERSION,
|
|
124
124
|
capabilities: {},
|
|
125
|
-
clientInfo: { name: 'opencode-idea', version: '0.0.
|
|
125
|
+
clientInfo: { name: 'opencode-idea', version: '0.0.17' },
|
|
126
126
|
},
|
|
127
127
|
}),
|
|
128
128
|
signal: controller.signal,
|
|
@@ -193,7 +193,7 @@ export async function callTool(port, projectPath, name, args = {}, options = {})
|
|
|
193
193
|
params: {
|
|
194
194
|
protocolVersion: PROTOCOL_VERSION,
|
|
195
195
|
capabilities: {},
|
|
196
|
-
clientInfo: { name: 'opencode-idea', version: '0.0.
|
|
196
|
+
clientInfo: { name: 'opencode-idea', version: '0.0.17' },
|
|
197
197
|
},
|
|
198
198
|
});
|
|
199
199
|
// The IDE's Streamable-HTTP endpoint binds follow-up requests to the session
|