ai-cli-mcp 2.9.1 → 2.11.0

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.
@@ -0,0 +1,28 @@
1
+ ## Summary
2
+
3
+ <!-- Briefly describe what this PR does -->
4
+
5
+ ## Type of Change
6
+
7
+ - [ ] feat: New feature
8
+ - [ ] fix: Bug fix
9
+ - [ ] refactor: Refactoring
10
+ - [ ] docs: Documentation
11
+ - [ ] test: Add or update tests
12
+ - [ ] chore: Build, CI, dependencies, etc.
13
+
14
+ ## Changes
15
+
16
+ <!-- List the main changes -->
17
+
18
+ -
19
+
20
+ ## Test Plan
21
+
22
+ - [ ] `npm run test:unit` passes
23
+ - [ ] `npm run build` passes
24
+ - [ ] Manual testing (if applicable)
25
+
26
+ ## Notes
27
+
28
+ <!-- Any additional context for reviewers -->
@@ -0,0 +1,276 @@
1
+ name: Watch Session PRs
2
+
3
+ on:
4
+ schedule:
5
+ - cron: '17 0 * * *'
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ contents: read
10
+ issues: write
11
+
12
+ jobs:
13
+ watch-codex-fork:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - name: Check Codex fork PR and update issue
17
+ uses: actions/github-script@v7
18
+ env:
19
+ UPSTREAM_OWNER: openai
20
+ UPSTREAM_REPO: codex
21
+ UPSTREAM_PR_NUMBER: '13537'
22
+ TARGET_ISSUE_NUMBER: '7'
23
+ COMMENT_MARKER: '<!-- codex-fork-pr-13537-status -->'
24
+ READY_LABEL: '[ready]'
25
+ STOP_LABEL: '[stop]'
26
+ with:
27
+ github-token: ${{ secrets.GITHUB_TOKEN }}
28
+ script: |
29
+ const owner = process.env.UPSTREAM_OWNER;
30
+ const repo = process.env.UPSTREAM_REPO;
31
+ const pull_number = Number(process.env.UPSTREAM_PR_NUMBER);
32
+ const issue_number = Number(process.env.TARGET_ISSUE_NUMBER);
33
+ const marker = process.env.COMMENT_MARKER;
34
+ const readyLabel = process.env.READY_LABEL;
35
+ const stopLabel = process.env.STOP_LABEL;
36
+
37
+ const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number });
38
+ const desiredLabel = pr.merged_at ? readyLabel : stopLabel;
39
+ const staleLabel = pr.merged_at ? stopLabel : readyLabel;
40
+ const { data: issue } = await github.rest.issues.get({
41
+ owner: context.repo.owner,
42
+ repo: context.repo.repo,
43
+ issue_number,
44
+ });
45
+
46
+ if (!issue.labels.some((label) => label.name === desiredLabel)) {
47
+ await github.rest.issues.addLabels({
48
+ owner: context.repo.owner,
49
+ repo: context.repo.repo,
50
+ issue_number,
51
+ labels: [desiredLabel],
52
+ });
53
+ core.info(`Added label ${desiredLabel} to issue #${issue_number}.`);
54
+ }
55
+
56
+ if (issue.labels.some((label) => label.name === staleLabel)) {
57
+ try {
58
+ await github.rest.issues.removeLabel({
59
+ owner: context.repo.owner,
60
+ repo: context.repo.repo,
61
+ issue_number,
62
+ name: staleLabel,
63
+ });
64
+ core.info(`Removed label ${staleLabel} from issue #${issue_number}.`);
65
+ } catch (error) {
66
+ core.warning(`Failed to remove label ${staleLabel}: ${error.message}`);
67
+ }
68
+ }
69
+
70
+ core.summary
71
+ .addHeading('Codex PR status')
72
+ .addTable([
73
+ [
74
+ { data: 'Field', header: true },
75
+ { data: 'Value', header: true },
76
+ ],
77
+ ['PR', `${owner}/${repo}#${pr.number}`],
78
+ ['Title', pr.title],
79
+ ['State', pr.state],
80
+ ['Merged', pr.merged_at ? 'yes' : 'no'],
81
+ ['Merged at', pr.merged_at ?? 'not merged'],
82
+ ['Updated at', pr.updated_at],
83
+ ['URL', pr.html_url],
84
+ ]);
85
+
86
+ const comments = await github.paginate(github.rest.issues.listComments, {
87
+ owner: context.repo.owner,
88
+ repo: context.repo.repo,
89
+ issue_number,
90
+ per_page: 100,
91
+ });
92
+
93
+ const existingComment = comments.find((comment) => comment.body?.includes(marker));
94
+ const body = [
95
+ marker,
96
+ 'upstream PR `openai/codex#13537` status watcher',
97
+ '',
98
+ `- PR: ${pr.html_url}`,
99
+ `- State: ${pr.state}`,
100
+ `- Merged: ${pr.merged_at ? 'yes' : 'no'}`,
101
+ `- Merged at: ${pr.merged_at ?? 'not merged'}`,
102
+ `- Updated at: ${pr.updated_at}`,
103
+ '',
104
+ pr.merged_at
105
+ ? 'This PR has been merged. `codex exec --fork <SESSION_ID> [PROMPT]` should become available once the change lands in the installed Codex version.'
106
+ : 'This PR is not merged yet. Non-interactive forking is still not available in released Codex builds.',
107
+ 'A new session ID is expected when using `--fork`; `resume` itself continues the same session.',
108
+ ].join('\n');
109
+
110
+ if (existingComment?.body === body) {
111
+ core.info(`Issue #${issue_number} status comment is already up to date.`);
112
+ await core.summary.addRaw(`No update needed: ${existingComment.html_url}\n`).write();
113
+ return;
114
+ }
115
+
116
+ if (existingComment) {
117
+ const { data: comment } = await github.rest.issues.updateComment({
118
+ owner: context.repo.owner,
119
+ repo: context.repo.repo,
120
+ comment_id: existingComment.id,
121
+ body,
122
+ });
123
+ core.info(`Updated issue #${issue_number}: ${comment.html_url}`);
124
+ await core.summary.addRaw(`Updated status comment: ${comment.html_url}\n`).write();
125
+ return;
126
+ }
127
+
128
+ const { data: comment } = await github.rest.issues.createComment({
129
+ owner: context.repo.owner,
130
+ repo: context.repo.repo,
131
+ issue_number,
132
+ body,
133
+ });
134
+ core.info(`Created issue #${issue_number} status comment: ${comment.html_url}`);
135
+ await core.summary.addRaw(`Created status comment: ${comment.html_url}\n`).write();
136
+
137
+ watch-gemini-session-work:
138
+ runs-on: ubuntu-latest
139
+ steps:
140
+ - name: Check Gemini session PRs and update issue
141
+ uses: actions/github-script@v7
142
+ env:
143
+ UPSTREAM_OWNER: google-gemini
144
+ UPSTREAM_REPO: gemini-cli
145
+ TARGET_ISSUE_NUMBER: '16'
146
+ COMMENT_MARKER: '<!-- gemini-session-pr-status -->'
147
+ WATCH_PR_NUMBERS: '17921,19629,19994'
148
+ READY_PR_NUMBERS: '17921,19629'
149
+ READY_LABEL: '[ready]'
150
+ STOP_LABEL: '[stop]'
151
+ with:
152
+ github-token: ${{ secrets.GITHUB_TOKEN }}
153
+ script: |
154
+ const owner = process.env.UPSTREAM_OWNER;
155
+ const repo = process.env.UPSTREAM_REPO;
156
+ const issue_number = Number(process.env.TARGET_ISSUE_NUMBER);
157
+ const marker = process.env.COMMENT_MARKER;
158
+ const pullNumbers = process.env.WATCH_PR_NUMBERS.split(',').map((value) => Number(value.trim()));
159
+ const readyPullNumbers = process.env.READY_PR_NUMBERS.split(',').map((value) => Number(value.trim()));
160
+ const readyLabel = process.env.READY_LABEL;
161
+ const stopLabel = process.env.STOP_LABEL;
162
+
163
+ const pulls = await Promise.all(
164
+ pullNumbers.map(async (pull_number) => {
165
+ const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number });
166
+ return pr;
167
+ }),
168
+ );
169
+ const isReady = pulls.some((pr) => readyPullNumbers.includes(pr.number) && Boolean(pr.merged_at));
170
+ const desiredLabel = isReady ? readyLabel : stopLabel;
171
+ const staleLabel = isReady ? stopLabel : readyLabel;
172
+
173
+ const { data: issue } = await github.rest.issues.get({
174
+ owner: context.repo.owner,
175
+ repo: context.repo.repo,
176
+ issue_number,
177
+ });
178
+
179
+ if (!issue.labels.some((label) => label.name === desiredLabel)) {
180
+ await github.rest.issues.addLabels({
181
+ owner: context.repo.owner,
182
+ repo: context.repo.repo,
183
+ issue_number,
184
+ labels: [desiredLabel],
185
+ });
186
+ core.info(`Added label ${desiredLabel} to issue #${issue_number}.`);
187
+ }
188
+
189
+ if (issue.labels.some((label) => label.name === staleLabel)) {
190
+ try {
191
+ await github.rest.issues.removeLabel({
192
+ owner: context.repo.owner,
193
+ repo: context.repo.repo,
194
+ issue_number,
195
+ name: staleLabel,
196
+ });
197
+ core.info(`Removed label ${staleLabel} from issue #${issue_number}.`);
198
+ } catch (error) {
199
+ core.warning(`Failed to remove label ${staleLabel}: ${error.message}`);
200
+ }
201
+ }
202
+
203
+ const lines = pulls.flatMap((pr) => [
204
+ `- \`${owner}/${repo}#${pr.number}\` ${pr.title}`,
205
+ ` - State: ${pr.state}`,
206
+ ` - Merged: ${pr.merged_at ? 'yes' : 'no'}`,
207
+ ` - Merged at: ${pr.merged_at ?? 'not merged'}`,
208
+ ` - Updated at: ${pr.updated_at}`,
209
+ ` - URL: ${pr.html_url}`,
210
+ ]);
211
+
212
+ core.summary
213
+ .addHeading('Gemini PR status')
214
+ .addTable([
215
+ [
216
+ { data: 'PR', header: true },
217
+ { data: 'State', header: true },
218
+ { data: 'Merged', header: true },
219
+ { data: 'Updated at', header: true },
220
+ ],
221
+ ...pulls.map((pr) => [
222
+ `${owner}/${repo}#${pr.number}`,
223
+ pr.state,
224
+ pr.merged_at ? 'yes' : 'no',
225
+ pr.updated_at,
226
+ ]),
227
+ ]);
228
+
229
+ const comments = await github.paginate(github.rest.issues.listComments, {
230
+ owner: context.repo.owner,
231
+ repo: context.repo.repo,
232
+ issue_number,
233
+ per_page: 100,
234
+ });
235
+
236
+ const existingComment = comments.find((comment) => comment.body?.includes(marker));
237
+ const body = [
238
+ marker,
239
+ 'upstream Gemini CLI session-related PR status watcher',
240
+ '',
241
+ 'Tracked PRs:',
242
+ ...lines,
243
+ '',
244
+ 'Interpretation:',
245
+ '- `#19994` is a fix for resumed session ID stats mismatch, not a fork/new-session feature.',
246
+ '- `#17921` is the `/new` command proposal for starting a fresh session while preserving the previous one.',
247
+ '- `#19629` is the `--session-id` proposal for explicit session UUID control.',
248
+ '- Even if these land, that still does not automatically imply Codex-style headless fork semantics from `--resume` alone.',
249
+ ].join('\n');
250
+
251
+ if (existingComment?.body === body) {
252
+ core.info(`Issue #${issue_number} Gemini status comment is already up to date.`);
253
+ await core.summary.addRaw(`No update needed: ${existingComment.html_url}\n`).write();
254
+ return;
255
+ }
256
+
257
+ if (existingComment) {
258
+ const { data: comment } = await github.rest.issues.updateComment({
259
+ owner: context.repo.owner,
260
+ repo: context.repo.repo,
261
+ comment_id: existingComment.id,
262
+ body,
263
+ });
264
+ core.info(`Updated issue #${issue_number}: ${comment.html_url}`);
265
+ await core.summary.addRaw(`Updated status comment: ${comment.html_url}\n`).write();
266
+ return;
267
+ }
268
+
269
+ const { data: comment } = await github.rest.issues.createComment({
270
+ owner: context.repo.owner,
271
+ repo: context.repo.repo,
272
+ issue_number,
273
+ body,
274
+ });
275
+ core.info(`Created issue #${issue_number} status comment: ${comment.html_url}`);
276
+ await core.summary.addRaw(`Created status comment: ${comment.html_url}\n`).write();
@@ -0,0 +1 @@
1
+ npm run test:unit
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # [2.11.0](https://github.com/mkXultra/ai-cli-mcp/compare/v2.10.0...v2.11.0) (2026-03-08)
2
+
3
+
4
+ ### Features
5
+
6
+ * ClaudeモデルへのReasoning Effort(--effort)サポートを追加 ([ac6b8cc](https://github.com/mkXultra/ai-cli-mcp/commit/ac6b8cc47a06a2d207258e85cc21b1d4ce83e5b2))
7
+
8
+ # [2.10.0](https://github.com/mkXultra/ai-cli-mcp/compare/v2.9.1...v2.10.0) (2026-03-07)
9
+
10
+
11
+ ### Features
12
+
13
+ * gpt-5.4モデルを追加しcodex-ultraエイリアスを更新 ([0fb15a6](https://github.com/mkXultra/ai-cli-mcp/commit/0fb15a635ab9d5450da16cb3b15132054c8c0dbe))
14
+
1
15
  ## [2.9.1](https://github.com/mkXultra/ai-cli-mcp/compare/v2.9.0...v2.9.1) (2026-03-07)
2
16
 
3
17
 
package/README.ja.md CHANGED
@@ -18,7 +18,7 @@ Cursorなどのエディタが、複雑な手順を伴う編集や操作に苦
18
18
  - 自動承認モードでGemini CLIを実行(`-y` を使用)
19
19
  - 複数のAIモデルのサポート:
20
20
  - Claude (sonnet, sonnet[1m], opus, opusplan, haiku)
21
- - Codex (gpt-5.3-codex, gpt-5.2-codex, gpt-5.1-codex-mini, gpt-5.1-codex-max, など)
21
+ - Codex (gpt-5.4, gpt-5.3-codex, gpt-5.2-codex, gpt-5.1-codex-mini, gpt-5.1-codex-max, など)
22
22
  - Gemini (gemini-2.5-pro, gemini-2.5-flash, gemini-3.1-pro-preview, gemini-3-pro-preview, gemini-3-flash-preview)
23
23
  - PID追跡によるバックグラウンドプロセスの管理
24
24
  - ツールからの構造化された出力の解析と返却
@@ -131,11 +131,11 @@ Claude CLI、Codex CLI、またはGemini CLIを使用してプロンプトを実
131
131
  - `prompt_file` (string, 任意): プロンプトを含むファイルへのパス。`prompt` または `prompt_file` のいずれかが必須です。絶対パス、または `workFolder` からの相対パスが指定可能です。
132
132
  - `workFolder` (string, 必須): CLIを実行する作業ディレクトリ。絶対パスである必要があります。
133
133
  - **モデル (Models):**
134
- - **Ultra エイリアス:** `claude-ultra`, `codex-ultra` (自動的に high-reasoning に設定), `gemini-ultra`
134
+ - **Ultra エイリアス:** `claude-ultra` (自動的に high effort に設定), `codex-ultra` (自動的に xhigh reasoning に設定), `gemini-ultra`
135
135
  - Claude: `sonnet`, `sonnet[1m]`, `opus`, `opusplan`, `haiku`
136
- - Codex: `gpt-5.3-codex`, `gpt-5.2-codex`, `gpt-5.1-codex-mini`, `gpt-5.1-codex-max`, `gpt-5.2`, `gpt-5.1`, `gpt-5`
136
+ - Codex: `gpt-5.4`, `gpt-5.3-codex`, `gpt-5.2-codex`, `gpt-5.1-codex-mini`, `gpt-5.1-codex-max`, `gpt-5.2`, `gpt-5.1`, `gpt-5`
137
137
  - Gemini: `gemini-2.5-pro`, `gemini-2.5-flash`, `gemini-3.1-pro-preview`, `gemini-3-pro-preview`, `gemini-3-flash-preview`
138
- - `reasoning_effort` (string, 任意): Codex専用。`model_reasoning_effort` を設定します(許容値: "low", "medium", "high", "xhigh")。
138
+ - `reasoning_effort` (string, 任意): Claude と Codex の推論制御。Claude では `--effort` を使います(許容値: "low", "medium", "high")。Codex では `model_reasoning_effort` を使います(許容値: "low", "medium", "high", "xhigh")。
139
139
  - `session_id` (string, 任意): 以前のセッションを再開するためのセッションID。対応モデル: haiku, sonnet, opus, gemini-2.5-pro, gemini-2.5-flash, gemini-3.1-pro-preview, gemini-3-pro-preview, gemini-3-flash-preview。
140
140
 
141
141
  ### `wait`
package/README.md CHANGED
@@ -18,7 +18,7 @@ This MCP server provides tools that can be used by LLMs to interact with AI CLI
18
18
  - Run Claude CLI with all permissions bypassed (using `--dangerously-skip-permissions`)
19
19
  - Execute Codex CLI with automatic approval mode (using `--full-auto`)
20
20
  - Execute Gemini CLI with automatic approval mode (using `-y`)
21
- - Support multiple AI models: Claude (sonnet, sonnet[1m], opus, opusplan, haiku), Codex (gpt-5.3-codex, gpt-5.2-codex, gpt-5.1-codex-mini, gpt-5.1-codex-max, gpt-5.2, gpt-5.1, gpt-5.1-codex, gpt-5-codex, gpt-5-codex-mini, gpt-5), and Gemini (gemini-2.5-pro, gemini-2.5-flash, gemini-3.1-pro-preview, gemini-3-pro-preview, gemini-3-flash-preview)
21
+ - Support multiple AI models: Claude (sonnet, sonnet[1m], opus, opusplan, haiku), Codex (gpt-5.4, gpt-5.3-codex, gpt-5.2-codex, gpt-5.1-codex-mini, gpt-5.1-codex-max, gpt-5.2, gpt-5.1, gpt-5.1-codex, gpt-5-codex, gpt-5-codex-mini, gpt-5), and Gemini (gemini-2.5-pro, gemini-2.5-flash, gemini-3.1-pro-preview, gemini-3-pro-preview, gemini-3-flash-preview)
22
22
  - Manage background processes with PID tracking
23
23
  - Parse and return structured outputs from both tools
24
24
 
@@ -130,11 +130,11 @@ Executes a prompt using Claude CLI, Codex CLI, or Gemini CLI. The appropriate CL
130
130
  - `prompt_file` (string, optional): Path to a file containing the prompt. Either `prompt` or `prompt_file` is required. Can be absolute path or relative to `workFolder`.
131
131
  - `workFolder` (string, required): The working directory for the CLI execution. Must be an absolute path.
132
132
  **Models:**
133
- - **Ultra Aliases:** `claude-ultra`, `codex-ultra` (defaults to high-reasoning), `gemini-ultra`
133
+ - **Ultra Aliases:** `claude-ultra` (defaults to high effort), `codex-ultra` (defaults to xhigh reasoning), `gemini-ultra`
134
134
  - Claude: `sonnet`, `sonnet[1m]`, `opus`, `opusplan`, `haiku`
135
- - Codex: `gpt-5.3-codex`, `gpt-5.2-codex`, `gpt-5.1-codex-mini`, `gpt-5.1-codex-max`, `gpt-5.2`, `gpt-5.1`, `gpt-5`
135
+ - Codex: `gpt-5.4`, `gpt-5.3-codex`, `gpt-5.2-codex`, `gpt-5.1-codex-mini`, `gpt-5.1-codex-max`, `gpt-5.2`, `gpt-5.1`, `gpt-5`
136
136
  - Gemini: `gemini-2.5-pro`, `gemini-2.5-flash`, `gemini-3.1-pro-preview`, `gemini-3-pro-preview`, `gemini-3-flash-preview`
137
- - `reasoning_effort` (string, optional): Codex only. Sets `model_reasoning_effort` (allowed: "low", "medium", "high", "xhigh").
137
+ - `reasoning_effort` (string, optional): Reasoning control for Claude and Codex. Claude uses `--effort` (allowed: "low", "medium", "high"). Codex uses `model_reasoning_effort` (allowed: "low", "medium", "high", "xhigh").
138
138
  - `session_id` (string, optional): Optional session ID to resume a previous session. Supported for: haiku, sonnet, opus, gemini-2.5-pro, gemini-2.5-flash, gemini-3.1-pro-preview, gemini-3-pro-preview, gemini-3-flash-preview.
139
139
 
140
140
  ### `wait`
@@ -25,8 +25,8 @@ describe('cli-builder', () => {
25
25
  it('should resolve claude-ultra to opus', () => {
26
26
  expect(resolveModelAlias('claude-ultra')).toBe('opus');
27
27
  });
28
- it('should resolve codex-ultra to gpt-5.3-codex', () => {
29
- expect(resolveModelAlias('codex-ultra')).toBe('gpt-5.3-codex');
28
+ it('should resolve codex-ultra to gpt-5.4', () => {
29
+ expect(resolveModelAlias('codex-ultra')).toBe('gpt-5.4');
30
30
  });
31
31
  it('should resolve gemini-ultra to gemini-3.1-pro-preview', () => {
32
32
  expect(resolveModelAlias('gemini-ultra')).toBe('gemini-3.1-pro-preview');
@@ -58,12 +58,17 @@ describe('cli-builder', () => {
58
58
  expect(getReasoningEffort('gpt-5.2', 'medium')).toBe('medium');
59
59
  expect(getReasoningEffort('gpt-5.2', 'high')).toBe('high');
60
60
  expect(getReasoningEffort('gpt-5.2', 'xhigh')).toBe('xhigh');
61
+ expect(getReasoningEffort('sonnet', 'high')).toBe('high');
62
+ expect(getReasoningEffort('', 'low')).toBe('low');
61
63
  });
62
64
  it('should throw for invalid reasoning effort value', () => {
63
65
  expect(() => getReasoningEffort('gpt-5.2', 'ultra')).toThrow('Invalid reasoning_effort: ultra. Allowed values: low, medium, high, xhigh.');
64
66
  });
65
- it('should throw for non-codex models', () => {
66
- expect(() => getReasoningEffort('sonnet', 'high')).toThrow('reasoning_effort is only supported for Codex models (gpt-*).');
67
+ it('should reject xhigh for claude models', () => {
68
+ expect(() => getReasoningEffort('sonnet', 'xhigh')).toThrow('Claude reasoning_effort supports only low, medium, high.');
69
+ });
70
+ it('should throw for unsupported model families', () => {
71
+ expect(() => getReasoningEffort('gemini-2.5-pro', 'high')).toThrow('reasoning_effort is only supported for Claude and Codex models.');
67
72
  });
68
73
  });
69
74
  describe('buildCliCommand', () => {
@@ -173,6 +178,48 @@ describe('cli-builder', () => {
173
178
  expect(cmd.resolvedModel).toBe('opus');
174
179
  expect(cmd.args).toContain('opus');
175
180
  });
181
+ it('should resolve claude-ultra and default to high effort', () => {
182
+ const cmd = buildCliCommand({
183
+ prompt: 'test',
184
+ workFolder: '/tmp',
185
+ model: 'claude-ultra',
186
+ cliPaths: DEFAULT_CLI_PATHS,
187
+ });
188
+ expect(cmd.args).toContain('--effort');
189
+ expect(cmd.args).toContain('high');
190
+ });
191
+ it('should build claude command with reasoning_effort using --effort', () => {
192
+ const cmd = buildCliCommand({
193
+ prompt: 'test',
194
+ workFolder: '/tmp',
195
+ model: 'sonnet',
196
+ reasoning_effort: 'medium',
197
+ cliPaths: DEFAULT_CLI_PATHS,
198
+ });
199
+ expect(cmd.args).toContain('--effort');
200
+ expect(cmd.args).toContain('medium');
201
+ });
202
+ it('should reject xhigh reasoning_effort for claude', () => {
203
+ expect(() => buildCliCommand({
204
+ prompt: 'test',
205
+ workFolder: '/tmp',
206
+ model: 'sonnet',
207
+ reasoning_effort: 'xhigh',
208
+ cliPaths: DEFAULT_CLI_PATHS,
209
+ })).toThrow('Claude reasoning_effort supports only low, medium, high.');
210
+ });
211
+ it('should allow overriding reasoning_effort for claude-ultra', () => {
212
+ const cmd = buildCliCommand({
213
+ prompt: 'test',
214
+ workFolder: '/tmp',
215
+ model: 'claude-ultra',
216
+ reasoning_effort: 'low',
217
+ cliPaths: DEFAULT_CLI_PATHS,
218
+ });
219
+ expect(cmd.args).toContain('--effort');
220
+ expect(cmd.args).toContain('low');
221
+ expect(cmd.args).not.toContain('high');
222
+ });
176
223
  });
177
224
  describe('codex agent', () => {
178
225
  it('should build codex command', () => {
@@ -221,7 +268,7 @@ describe('cli-builder', () => {
221
268
  cliPaths: DEFAULT_CLI_PATHS,
222
269
  });
223
270
  expect(cmd.agent).toBe('codex');
224
- expect(cmd.resolvedModel).toBe('gpt-5.3-codex');
271
+ expect(cmd.resolvedModel).toBe('gpt-5.4');
225
272
  expect(cmd.args).toContain('-c');
226
273
  expect(cmd.args).toContain('model_reasoning_effort=xhigh');
227
274
  });
@@ -251,7 +251,7 @@ describe('Argument Validation Tests', () => {
251
251
  }
252
252
  })).rejects.toThrow(/reasoning_effort/i);
253
253
  });
254
- it('should reject reasoning_effort for non-codex models', async () => {
254
+ it('should reject reasoning_effort for unsupported model families', async () => {
255
255
  await setupServer();
256
256
  const handler = handlers.get('callTool');
257
257
  await expect(handler({
@@ -260,7 +260,7 @@ describe('Argument Validation Tests', () => {
260
260
  arguments: {
261
261
  prompt: 'test',
262
262
  workFolder: '/tmp',
263
- model: 'sonnet',
263
+ model: 'gemini-2.5-pro',
264
264
  reasoning_effort: 'low'
265
265
  }
266
266
  }
@@ -3,10 +3,20 @@ import { resolve as pathResolve, isAbsolute } from 'node:path';
3
3
  // Model alias mappings for user-friendly model names
4
4
  export const MODEL_ALIASES = {
5
5
  'claude-ultra': 'opus',
6
- 'codex-ultra': 'gpt-5.3-codex',
6
+ 'codex-ultra': 'gpt-5.4',
7
7
  'gemini-ultra': 'gemini-3.1-pro-preview'
8
8
  };
9
9
  export const ALLOWED_REASONING_EFFORTS = new Set(['low', 'medium', 'high', 'xhigh']);
10
+ const CLAUDE_REASONING_EFFORTS = new Set(['low', 'medium', 'high']);
11
+ function getAgentForModel(model) {
12
+ if (model.startsWith('gpt-')) {
13
+ return 'codex';
14
+ }
15
+ if (model.startsWith('gemini')) {
16
+ return 'gemini';
17
+ }
18
+ return 'claude';
19
+ }
10
20
  /**
11
21
  * Resolves model aliases to their full model names
12
22
  * @param model - The model name or alias to resolve
@@ -32,8 +42,12 @@ export function getReasoningEffort(model, rawValue) {
32
42
  if (!ALLOWED_REASONING_EFFORTS.has(normalized)) {
33
43
  throw new Error(`Invalid reasoning_effort: ${rawValue}. Allowed values: low, medium, high, xhigh.`);
34
44
  }
35
- if (!model.startsWith('gpt-')) {
36
- throw new Error('reasoning_effort is only supported for Codex models (gpt-*).');
45
+ const agent = getAgentForModel(model);
46
+ if (agent === 'gemini') {
47
+ throw new Error('reasoning_effort is only supported for Claude and Codex models.');
48
+ }
49
+ if (agent === 'claude' && !CLAUDE_REASONING_EFFORTS.has(normalized)) {
50
+ throw new Error('Claude reasoning_effort supports only low, medium, high.');
37
51
  }
38
52
  return normalized;
39
53
  }
@@ -83,23 +97,18 @@ export function buildCliCommand(options) {
83
97
  // Resolve model
84
98
  const rawModel = options.model || '';
85
99
  const resolvedModel = resolveModelAlias(rawModel);
86
- // Special handling for codex-ultra: default to high reasoning effort if not specified
100
+ const agent = getAgentForModel(resolvedModel);
101
+ // Special handling for ultra aliases: default to higher reasoning if not specified
87
102
  let reasoningEffortArg = options.reasoning_effort;
88
- if (rawModel === 'codex-ultra' && !reasoningEffortArg) {
89
- reasoningEffortArg = 'xhigh';
103
+ if (!reasoningEffortArg) {
104
+ if (rawModel === 'codex-ultra') {
105
+ reasoningEffortArg = 'xhigh';
106
+ }
107
+ else if (rawModel === 'claude-ultra') {
108
+ reasoningEffortArg = 'high';
109
+ }
90
110
  }
91
111
  const reasoningEffort = getReasoningEffort(resolvedModel, reasoningEffortArg);
92
- // Determine agent
93
- let agent;
94
- if (resolvedModel.startsWith('gpt-')) {
95
- agent = 'codex';
96
- }
97
- else if (resolvedModel.startsWith('gemini')) {
98
- agent = 'gemini';
99
- }
100
- else {
101
- agent = 'claude';
102
- }
103
112
  // Build CLI path and args
104
113
  let cliPath;
105
114
  let args;
@@ -136,6 +145,9 @@ export function buildCliCommand(options) {
136
145
  if (options.session_id && typeof options.session_id === 'string') {
137
146
  args.push('-r', options.session_id, '--fork-session');
138
147
  }
148
+ if (reasoningEffort) {
149
+ args.push('--effort', reasoningEffort);
150
+ }
139
151
  args.push('-p', prompt);
140
152
  if (resolvedModel) {
141
153
  args.push('--model', resolvedModel);
package/dist/cli.js CHANGED
@@ -40,7 +40,7 @@ Options:
40
40
  --prompt Prompt string (mutually exclusive with --prompt_file)
41
41
  --prompt_file Path to a file containing the prompt
42
42
  --session_id Session ID to resume
43
- --reasoning_effort Codex only: low, medium, high, xhigh
43
+ --reasoning_effort Claude/Codex: Claude=low|medium|high, Codex=low|medium|high|xhigh
44
44
  --help Show this help message
45
45
 
46
46
  Raw CLI output goes to stdout. Use cli.run.parse to parse the output:
package/dist/server.js CHANGED
@@ -116,7 +116,7 @@ export class ClaudeCodeServer {
116
116
  **IMPORTANT**: This tool now returns immediately with a PID. Use other tools to check status and get results.
117
117
 
118
118
  **Supported models**:
119
- "claude-ultra", "codex-ultra", "gemini-ultra", "sonnet", "sonnet[1m]", "opus", "opusplan", "haiku", "gpt-5.3-codex", "gpt-5.2-codex", "gpt-5.1-codex-mini", "gpt-5.1-codex-max", "gpt-5.2", "gpt-5.1", "gpt-5.1-codex", "gpt-5-codex", "gpt-5-codex-mini", "gpt-5", "gemini-2.5-pro", "gemini-2.5-flash", "gemini-3.1-pro-preview", "gemini-3-pro-preview", "gemini-3-flash-preview"
119
+ "claude-ultra", "codex-ultra", "gemini-ultra", "sonnet", "sonnet[1m]", "opus", "opusplan", "haiku", "gpt-5.4", "gpt-5.3-codex", "gpt-5.2-codex", "gpt-5.1-codex-mini", "gpt-5.1-codex-max", "gpt-5.2", "gpt-5.1", "gpt-5.1-codex", "gpt-5-codex", "gpt-5-codex-mini", "gpt-5", "gemini-2.5-pro", "gemini-2.5-flash", "gemini-3.1-pro-preview", "gemini-3-pro-preview", "gemini-3-flash-preview"
120
120
 
121
121
  **Prompt input**: You must provide EITHER prompt (string) OR prompt_file (file path), but not both.
122
122
 
@@ -144,11 +144,11 @@ export class ClaudeCodeServer {
144
144
  },
145
145
  model: {
146
146
  type: 'string',
147
- description: 'The model to use. Aliases: "claude-ultra", "codex-ultra" (auto high-reasoning), "gemini-ultra". Standard: "sonnet", "sonnet[1m]", "opus", "opusplan", "haiku", "gpt-5.3-codex", "gpt-5.2-codex", "gpt-5.1-codex-mini", "gpt-5.1", "gemini-2.5-pro", "gemini-3.1-pro-preview", "gemini-3-pro-preview", "gemini-3-flash-preview", etc.',
147
+ description: 'The model to use. Aliases: "claude-ultra" (auto high effort), "codex-ultra" (auto xhigh reasoning), "gemini-ultra". Standard: "sonnet", "sonnet[1m]", "opus", "opusplan", "haiku", "gpt-5.4", "gpt-5.3-codex", "gpt-5.2-codex", "gpt-5.1-codex-mini", "gpt-5.1", "gemini-2.5-pro", "gemini-3.1-pro-preview", "gemini-3-pro-preview", "gemini-3-flash-preview", etc.',
148
148
  },
149
149
  reasoning_effort: {
150
150
  type: 'string',
151
- description: 'Codex only. Sets model_reasoning_effort. Allowed: "low", "medium", "high", "xhigh".',
151
+ description: 'Reasoning control for Claude and Codex. Claude uses --effort with "low", "medium", "high". Codex uses model_reasoning_effort with "low", "medium", "high", "xhigh".',
152
152
  },
153
153
  session_id: {
154
154
  type: 'string',
@@ -0,0 +1,154 @@
1
+ # AI CLI MCP Server - Concept
2
+
3
+ ## What is this?
4
+
5
+ AI CLI MCP Server (`ai-cli-mcp`) は、複数のAI CLIツール(Claude Code, Codex, Gemini)をMCPプロトコル経由でバックグラウンド実行するサーバーである。
6
+
7
+ ## 解決する課題
8
+
9
+ ### 1. 単一エージェントのボトルネック
10
+
11
+ CursorなどのAI IDE は内部で1つのAIエージェントを逐次実行する。複雑なマルチステップ作業(リファクタリング + テスト作成 + ドキュメント更新など)では、1つずつ順番に処理するしかなく時間がかかる。
12
+
13
+ ### 2. モデル/プロバイダーのロックイン
14
+
15
+ IDE が採用するAIモデルに依存し、タスクごとに最適なモデルを選択できない。コード生成には Claude Opus、高速な軽作業には Haiku、といった使い分けができない。
16
+
17
+ ### 3. AI CLI の非同期実行の欠如
18
+
19
+ Claude Code や Codex CLI は対話的に使うことを前提としており、バックグラウンドでの非同期プロセス管理をネイティブにはサポートしていない。
20
+
21
+ ## Core Idea
22
+
23
+ **MCP を「AIエージェントのプロセスマネージャー」として使う。**
24
+
25
+ ```
26
+ MCP Client (Cursor, Claude Code, etc.)
27
+
28
+ ├─ run(prompt, model="opus") → PID 1234 (即座に返却)
29
+ ├─ run(prompt, model="gpt-5.2-codex") → PID 1235
30
+ ├─ run(prompt, model="gemini-2.5-pro") → PID 1236
31
+
32
+ ├─ list_processes() → 実行状況一覧
33
+ ├─ get_result(pid) → 個別結果取得
34
+ └─ wait(pids) → 全完了待ち
35
+ ```
36
+
37
+ - **Fire-and-forget**: `run` は即座にPIDを返し、呼び出し側はブロックされない
38
+ - **Blocking wait**: `wait(pids)` は指定プロセスが全完了するまでブロックする。利用者がポーリングループを組む必要はない。`run` で複数タスクを投げ、自分の作業を進めた後、`wait` 一発で全結果を回収するのが基本フロー
39
+ - **マルチモデル**: プロンプトごとに異なるAIモデルを選択可能
40
+ - **プロセスライフサイクル管理**: 起動・監視・結果取得・強制終了を統一APIで提供
41
+
42
+ ### 基本フロー
43
+
44
+ ```
45
+ run(task1) → PID 1
46
+ run(task2) → PID 2
47
+ run(task3) → PID 3
48
+
49
+ (呼び出し側は自分の作業を続行)
50
+
51
+ wait([PID 1, 2, 3]) → ブロック → 全完了後に結果をまとめて返却
52
+ ```
53
+
54
+ ## Core Responsibility
55
+
56
+ このツールの責務は3つに集約される:
57
+
58
+ ### 1. どのAI CLIからでも同じプロンプトで使える
59
+
60
+ このMCPサーバーは Claude Code / Gemini CLI / Codex CLI のいずれをホスト(呼び出し元)としても、同じツール名・同じ引数・同じプロンプトで動作する。ホスト側のAIがどのプロバイダーであっても、統一されたMCPインターフェースを通じて同一の体験を提供する。
61
+
62
+ ### 2. CLI差異の完全な隠蔽
63
+
64
+ 利用者(主にAIエージェント)は Claude Code / Codex / Gemini の個別仕様を一切知る必要がない。
65
+
66
+ - パーミッションフラグの違い(`--dangerously-skip-permissions` / `--full-auto` / `-y`)
67
+ - 出力形式の違い(Claude の JSON / Codex のログ / Gemini の出力)
68
+ - セッション管理の違い(`--session-id` / `--session` / `-s`)
69
+ - モデル名の指定方法の違い
70
+
71
+ これらはすべて内部で吸収される。利用者は「モデル名とプロンプトを渡すだけ」でよい。
72
+
73
+ ### 3. AI-Friendly な返り値
74
+
75
+ 返り値は人間ではなく **AIエージェントが消費する** ことを前提に設計する。
76
+
77
+ - 各CLIの生出力(JSON、ログ、テキスト)をパースし、構造化されたオブジェクトとして返す
78
+ - `session_id` や `exitCode` などのメタデータを統一フォーマットで付与する
79
+ - `verbose` フラグで必要に応じてツール使用履歴も提供する
80
+ - AIが次のアクションを判断しやすい、一貫した構造を維持する
81
+
82
+ ## Design Principles
83
+
84
+ ### Thin Wrapper, Not a Framework
85
+
86
+ 各AI CLIの既存機能をそのまま活かし、MCPのインターフェースでラップするだけに留める。独自のプロンプト処理やフィルタリングは行わない。
87
+
88
+ ### Immediate Return
89
+
90
+ `run` は常に即座にPIDを返す。重い処理のブロッキングを避け、呼び出し元が他の作業を並行して進められるようにする。
91
+
92
+ ### CLI Agnostic
93
+
94
+ Claude / Codex / Gemini のCLI差異(フラグ、出力形式)を内部で吸収し、統一されたインターフェースを提供する。新しいCLIの追加も最小限の変更で可能な構造にする。
95
+
96
+ ## Architecture Overview
97
+
98
+ ```
99
+ src/
100
+ ├── server.ts # MCP Server 本体 (ツールハンドラ + プロセス管理)
101
+ ├── cli-builder.ts # モデル名 → CLI コマンド構築 (パス解決・引数組み立て)
102
+ ├── cli-utils.ts # CLI バイナリ検出・デバッグログ
103
+ ├── parsers.ts # 各CLI出力のパース (Claude JSON / Codex logs / Gemini)
104
+ ├── cli.ts # スタンドアロン CLI エントリポイント
105
+ └── cli-parse.ts # パース単体テスト用エントリポイント
106
+ ```
107
+
108
+ ## Session Stacking
109
+
110
+ `session_id` を使ってコンテキストを積み重ね、効率的にタスクを実行する推奨パターン。初回の `run` で構築したコンテキストを後続タスクに引き継ぎ、再読み込みコストを削減する。
111
+
112
+ 詳細は [Session Stacking](./session-stacking.md) を参照。
113
+
114
+ ## Model Aliases (Ultra)
115
+
116
+ `claude-ultra` / `codex-ultra` / `gemini-ultra` というエイリアスを提供している。
117
+
118
+ ```
119
+ claude-ultra → opus
120
+ codex-ultra → gpt-5.4 (+ reasoning_effort: high)
121
+ gemini-ultra → gemini-3.1-pro-preview
122
+ ```
123
+
124
+ **設計意図**: AI プロバイダーのモデル名は頻繁に変わる。利用者(特にAIエージェント)が個々のモデル名の変遷を追う必要がないよう、「そのプロバイダーの最強モデル」を指す安定したエイリアスを提供する。マッピング先はサーバー側で更新するだけで、利用者のプロンプトを変更する必要がない。
125
+
126
+ ## Security Model
127
+
128
+ このツールは **信頼された環境でのみ使用する** ことを前提としている。
129
+
130
+ - Claude Code は `--dangerously-skip-permissions` で実行される(すべてのファイル操作・コマンド実行が無許可で行われる)
131
+ - Codex は `--full-auto` で実行される
132
+ - Gemini は `-y`(自動承認)で実行される
133
+
134
+ つまり、このMCPサーバーに接続できるクライアントは、ローカルマシン上で **任意のコード実行が可能** である。ネットワーク越しの不特定多数への公開や、信頼できないクライアントからのアクセスは想定していない。
135
+
136
+ ## Constraints
137
+
138
+ | 制約 | 詳細 |
139
+ |---|---|
140
+ | インメモリプロセス管理 | プロセス情報はサーバーのメモリ上にのみ保持される。サーバー再起動で全プロセス情報が消失する |
141
+ | stdio トランスポートのみ | HTTP/WebSocket 等のリモートトランスポートは未サポート。ローカル実行が前提 |
142
+ | 単一マシン | 分散実行やリモートマシンへのプロセス委譲はサポートしない |
143
+ | CLI の事前セットアップ必須 | 各AI CLIのインストール・認証はユーザーが事前に完了させる必要がある |
144
+
145
+ ## Key Technical Decisions
146
+
147
+ | 決定 | 理由 |
148
+ |---|---|
149
+ | `node:child_process.spawn` でプロセス管理 | 軽量で直接的。外部依存なしにPIDベースの管理が可能 |
150
+ | `--dangerously-skip-permissions` (Claude) | MCP経由の自動実行には非対話モードが必須 |
151
+ | `--full-auto` (Codex) / `-y` (Gemini) | 同上。各CLIの自動承認モード |
152
+ | `session_id` サポート | コンテキストキャッシュにより、大規模コードベースの読み込みコストを複数タスクで共有 |
153
+ | 出力パーサーの分離 | CLI出力形式の変更に対して個別に対応可能 |
154
+ | npx 配布 | インストール不要でMCP設定に直接記述可能 |
package/docs/prd.md ADDED
@@ -0,0 +1,146 @@
1
+ # AI CLI MCP Server - Product Requirements Document
2
+
3
+ ## Product Overview
4
+
5
+ | 項目 | 内容 |
6
+ |---|---|
7
+ | プロダクト名 | AI CLI MCP Server (`ai-cli-mcp`) |
8
+ | npm パッケージ | [ai-cli-mcp](https://www.npmjs.com/package/ai-cli-mcp) |
9
+ | 現バージョン | 2.8.2 |
10
+ | ライセンス | MIT |
11
+ | ターゲットユーザー | MCP対応AI IDE / CLI を利用する開発者 |
12
+
13
+ ## Problem Statement
14
+
15
+ AI支援開発において、以下の制約がユーザーの生産性を阻害している:
16
+
17
+ 1. **逐次処理の制約**: IDEのAIエージェントは1タスクずつしか処理できず、並行作業ができない
18
+ 2. **モデル選択の制約**: IDEが提供するモデルに限定され、タスクに最適なモデルを選べない
19
+ 3. **環境の制約**: AI CLIツールの豊富な機能(ファイル操作、Git操作、Web検索等)をIDE内から活用できない
20
+
21
+ ## Goals
22
+
23
+ - **ホスト非依存**: Claude Code / Gemini CLI / Codex CLI のどれをホスト(呼び出し元)として使っても、同じプロンプト・同じインターフェースで動作すること
24
+ - **CLI差異の隠蔽**: 利用者が呼び出し先の Claude Code / Codex / Gemini の個別仕様(フラグ、出力形式、セッション管理等)を意識せず、モデル名とプロンプトだけで使えること
25
+ - **AI-Friendly な返り値**: 各CLIの生出力をパースし、AIエージェントが次のアクションを判断しやすい構造化データとして返すこと
26
+ - **並行実行**: MCP対応の任意のクライアントから、複数のAI CLIエージェントを並行実行できること
27
+ - **最小セットアップ**: npx 一行で起動可能であること
28
+
29
+ ## Non-Goals
30
+
31
+ - AI CLI ツール自体の機能拡張
32
+ - 独自のプロンプトエンジニアリングやチェーン処理
33
+ - Web UI やダッシュボードの提供
34
+ - AI CLI ツールのインストールや認証の自動化
35
+ - 人間向けのリッチなフォーマット出力(返り値はAI消費が前提)
36
+
37
+ ## Functional Requirements
38
+
39
+ ### FR-1: プロセス実行 (`run`)
40
+
41
+ - ユーザーがプロンプト(文字列 or ファイルパス)、作業ディレクトリ、モデル名を指定してAIエージェントを起動できる
42
+ - プロセスはバックグラウンドで実行され、即座にPIDが返却される
43
+ - モデル名から適切なCLI(Claude / Codex / Gemini)が自動選択される
44
+ - Ultra エイリアス(`claude-ultra`, `codex-ultra`, `gemini-ultra`)による簡易モデル指定をサポート
45
+ - `session_id` による前回セッションの継続をサポート(Claude, Gemini)
46
+ - `reasoning_effort` による推論深度の指定をサポート(Codex)
47
+
48
+ ### FR-2: プロセス一覧 (`list_processes`)
49
+
50
+ - 実行中・完了・失敗の全プロセスをPID・エージェント種別・ステータスとともに一覧表示できる
51
+
52
+ ### FR-3: 結果取得 (`get_result`)
53
+
54
+ - PIDを指定して、プロセスの出力(パース済み)とメタデータを取得できる
55
+ - `verbose` オプションでツール使用履歴等の詳細情報を取得できる
56
+ - `session_id` がある場合は結果に含まれる
57
+
58
+ ### FR-4: 一括待機 (`wait`)
59
+
60
+ - 複数PIDを指定して、全プロセスの完了を待機できる
61
+ - 呼び出し側がポーリングする必要はなく、`wait` 自体がブロックして完了まで待つ設計
62
+ - `run` → (自分の作業) → `wait` で結果回収、が基本フロー
63
+ - タイムアウト指定が可能(デフォルト: 180秒)
64
+
65
+ ### FR-5: プロセス終了 (`kill_process`)
66
+
67
+ - PIDを指定して実行中のプロセスをSIGTERMで終了できる
68
+
69
+ ### FR-6: クリーンアップ (`cleanup_processes`)
70
+
71
+ - 完了・失敗したプロセスをプロセスリストから削除し、メモリを解放できる
72
+
73
+ ## Non-Functional Requirements
74
+
75
+ ### NFR-1: パフォーマンス
76
+
77
+ - `run` のレスポンスタイムはプロセスの実行時間に依存せず、即座に返却すること
78
+ - 複数プロセスの同時実行をサポートすること
79
+
80
+ ### NFR-2: 互換性
81
+
82
+ - Node.js v20 以上で動作すること
83
+ - MCP プロトコル仕様に準拠すること(`@modelcontextprotocol/sdk` 使用)
84
+ - stdio トランスポートで動作すること
85
+
86
+ ### NFR-3: 信頼性
87
+
88
+ - CLIプロセスのクラッシュを適切にハンドリングし、ステータスに反映すること
89
+ - プロセスの stdout / stderr を確実に収集すること
90
+
91
+ ### NFR-4: 運用性
92
+
93
+ - `npx ai-cli-mcp@latest` のみで起動可能であること
94
+ - 環境変数によるCLIパスのカスタマイズが可能であること
95
+ - `MCP_CLAUDE_DEBUG` によるデバッグログ出力をサポートすること
96
+
97
+ ## Supported Models
98
+
99
+ | Provider | Models |
100
+ |---|---|
101
+ | Claude | `sonnet`, `sonnet[1m]`, `opus`, `opusplan`, `haiku` |
102
+ | Codex | `gpt-5.4`, `gpt-5.3-codex`, `gpt-5.2-codex`, `gpt-5.1-codex-mini`, `gpt-5.1-codex-max`, `gpt-5.2`, `gpt-5.1`, `gpt-5` |
103
+ | Gemini | `gemini-2.5-pro`, `gemini-2.5-flash`, `gemini-3.1-pro-preview`, `gemini-3-pro-preview`, `gemini-3-flash-preview` |
104
+ | Ultra aliases | `claude-ultra`, `codex-ultra`, `gemini-ultra` |
105
+
106
+ ## User Scenarios
107
+
108
+ ### Scenario 1: 並列タスク実行
109
+
110
+ 1. ユーザーがMCPクライアント経由で3つの `run` を発行(リファクタリング / テスト作成 / ドキュメント更新)
111
+ 2. それぞれ異なるモデルで即座に起動、PIDが返却される
112
+ 3. ユーザーは別作業を進めつつ、`wait` で全完了を待機
113
+ 4. 各結果をまとめて確認
114
+
115
+ ### Scenario 2: Session Stacking(推奨パターン)
116
+
117
+ `session_id` によるセッション再開を活用し、コンテキストを積み重ねて効率的にタスクを実行する。
118
+
119
+ 1. `opus` で大規模コードベースを読み込み、`session_id` を取得
120
+ 2. 同じ `session_id` を後続の `run` に渡して複数タスクを並行起動
121
+ 3. コンテキストの再読み込みコストなしに、共有コンテキスト上で実行される
122
+ 4. さらに後続タスクの `session_id` を使って追加の深掘りも可能(段階的スタッキング)
123
+
124
+ ポーリング不要: 手順2の後は `wait` で全完了をブロッキング待機すればよい。
125
+
126
+ **注意**: 多段スタッキング(手順4)に完全対応しているのは Claude のみ。Codex / Gemini は1回のセッション再開は可能だが、再開後に新しい `session_id` が返らないため、それ以上の連鎖はできない。詳細は [Session Stacking](./session-stacking.md) を参照。
127
+
128
+ ## Security Model
129
+
130
+ 本プロダクトは **信頼されたローカル環境** での使用を前提とする。
131
+
132
+ - 各AI CLIは自動承認モード(`--dangerously-skip-permissions` / `--full-auto` / `-y`)で実行されるため、接続クライアントはローカルマシン上で任意のコード実行が可能になる
133
+ - ネットワーク越しの不特定多数への公開は想定しない
134
+ - セキュリティ境界は「MCPサーバーへの接続可否」で制御される
135
+
136
+ ## Constraints
137
+
138
+ - プロセス情報はインメモリのみ。サーバー再起動で消失する(永続化なし)
139
+ - stdio トランスポートのみ対応。リモート接続は未サポート
140
+ - 単一マシンでの実行が前提。分散実行はサポートしない
141
+ - 各AI CLIの事前インストール・認証はユーザー責任
142
+
143
+ ## Release History (Notable)
144
+
145
+ - **v1.x**: Claude Code MCP として Claude CLI のみサポート
146
+ - **v2.x**: `ai-cli-mcp` にリネーム、Codex / Gemini サポート追加、非同期実行モデルに移行
@@ -0,0 +1,67 @@
1
+ # Session Stacking
2
+
3
+ ## 概要
4
+
5
+ Session Stacking は、`session_id` を使ってコンテキストを積み重ね、効率的にタスクを実行する推奨パターンである。
6
+
7
+ 最初の `run` で構築したコンテキスト(コードベースの理解等)を `session_id` 経由で後続タスクに引き継ぐことで、同じ情報の再読み込みを避ける。
8
+
9
+ ## 基本フロー
10
+
11
+ ```
12
+ Step 1: コンテキスト構築
13
+ run("src/を全部読んで構造を理解して", model="opus")
14
+ → 結果: { session_id: "abc-123", ... }
15
+
16
+ Step 2: コンテキストを再利用して並行タスク実行
17
+ run("utilsをリファクタして", session_id="abc-123", model="sonnet") → PID 1
18
+ run("READMEを更新して", session_id="abc-123", model="haiku") → PID 2
19
+
20
+ Step 3: 結果回収
21
+ wait([PID 1, PID 2]) → ブロッキング待機 → 全結果返却
22
+ ```
23
+
24
+ ## 利点
25
+
26
+ - **コスト削減**: 大規模コードベースの再読み込みを避けられる
27
+ - **コンテキスト共有**: 1回の理解をベースに複数の派生タスクを実行できる
28
+ - **段階的な深掘り**: 概要把握 → 詳細分析 → 実装、とセッションを積み重ねられる
29
+
30
+ ## 多段スタッキング
31
+
32
+ Session Stacking の真価は、セッションを2段、3段と重ねて深掘りしていける点にある。
33
+
34
+ ```
35
+ Step 1: 全体理解
36
+ run("src/を全部読んで構造を理解して", model="opus")
37
+ → session_id: "abc-123"
38
+
39
+ Step 2: 詳細分析(Step 1 のコンテキストを継承)
40
+ run("認証周りの問題点を洗い出して", session_id="abc-123", model="opus")
41
+ → session_id: "def-456" ← 新しい session_id が返る
42
+
43
+ Step 3: 実装(Step 1 + Step 2 のコンテキストを継承)
44
+ run("洗い出した問題を修正して", session_id="def-456", model="sonnet")
45
+ ```
46
+
47
+ 各ステップのコンテキストが累積されるため、後段のタスクほど深い理解の上で実行される。
48
+
49
+ ## 各CLIの対応状況
50
+
51
+ | CLI | 1回のセッション再開 | 多段スタッキング | 備考 |
52
+ |---|---|---|---|
53
+ | Claude | OK | **OK** | 再開後に新しい `session_id` が返る。`--fork-session` でセッションをフォークするため、元のセッションも保持される |
54
+ | Codex | OK | **NG** | 再開後に新しい `session_id` が返らない。2段(初回 + 1回再開)まで |
55
+ | Gemini | OK | **NG** | 再開後に新しい `session_id` が返らない。2段(初回 + 1回再開)まで |
56
+
57
+ **完全な多段 Session Stacking ができるのは現時点では Claude のみ。** Codex / Gemini は1回のセッション再開には対応しているが、再開時に新しい `session_id` が発行されないため、それ以上の連鎖はできない。これは各CLIの制約であり、本ツール側の制限ではない。
58
+
59
+ ## 内部実装
60
+
61
+ 各CLIのセッション再開方法の違いは `cli-builder.ts` 内で吸収される。利用者は `session_id` パラメータを渡すだけでよい。
62
+
63
+ | CLI | 内部で実行されるフラグ |
64
+ |---|---|
65
+ | Claude | `-r <session_id> --fork-session` |
66
+ | Gemini | `-r <session_id>` |
67
+ | Codex | `exec resume <session_id>` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-cli-mcp",
3
- "version": "2.9.1",
3
+ "version": "2.11.0",
4
4
  "mcpName": "io.github.mkXultra/ai-cli-mcp",
5
5
  "description": "MCP server for AI CLI tools (Claude, Codex, and Gemini) with background process management",
6
6
  "author": "mkXultra",
@@ -20,7 +20,8 @@
20
20
  "test:coverage": "npm run build && vitest --coverage --config vitest.config.unit.ts",
21
21
  "test:watch": "vitest --watch",
22
22
  "cli.run": "tsx src/cli.ts",
23
- "cli.run.parse": "tsx src/cli-parse.ts"
23
+ "cli.run.parse": "tsx src/cli-parse.ts",
24
+ "prepare": "husky"
24
25
  },
25
26
  "dependencies": {
26
27
  "@modelcontextprotocol/sdk": "^1.11.2",
@@ -33,6 +34,7 @@
33
34
  "@semantic-release/git": "^10.0.1",
34
35
  "@types/node": "^22.15.17",
35
36
  "@vitest/coverage-v8": "^2.1.8",
37
+ "husky": "^9.1.7",
36
38
  "semantic-release": "^25.0.2",
37
39
  "tsx": "^4.19.4",
38
40
  "typescript": "^5.8.3",
@@ -36,8 +36,8 @@ describe('cli-builder', () => {
36
36
  expect(resolveModelAlias('claude-ultra')).toBe('opus');
37
37
  });
38
38
 
39
- it('should resolve codex-ultra to gpt-5.3-codex', () => {
40
- expect(resolveModelAlias('codex-ultra')).toBe('gpt-5.3-codex');
39
+ it('should resolve codex-ultra to gpt-5.4', () => {
40
+ expect(resolveModelAlias('codex-ultra')).toBe('gpt-5.4');
41
41
  });
42
42
 
43
43
  it('should resolve gemini-ultra to gemini-3.1-pro-preview', () => {
@@ -76,6 +76,8 @@ describe('cli-builder', () => {
76
76
  expect(getReasoningEffort('gpt-5.2', 'medium')).toBe('medium');
77
77
  expect(getReasoningEffort('gpt-5.2', 'high')).toBe('high');
78
78
  expect(getReasoningEffort('gpt-5.2', 'xhigh')).toBe('xhigh');
79
+ expect(getReasoningEffort('sonnet', 'high')).toBe('high');
80
+ expect(getReasoningEffort('', 'low')).toBe('low');
79
81
  });
80
82
 
81
83
  it('should throw for invalid reasoning effort value', () => {
@@ -84,9 +86,15 @@ describe('cli-builder', () => {
84
86
  );
85
87
  });
86
88
 
87
- it('should throw for non-codex models', () => {
88
- expect(() => getReasoningEffort('sonnet', 'high')).toThrow(
89
- 'reasoning_effort is only supported for Codex models (gpt-*).'
89
+ it('should reject xhigh for claude models', () => {
90
+ expect(() => getReasoningEffort('sonnet', 'xhigh')).toThrow(
91
+ 'Claude reasoning_effort supports only low, medium, high.'
92
+ );
93
+ });
94
+
95
+ it('should throw for unsupported model families', () => {
96
+ expect(() => getReasoningEffort('gemini-2.5-pro', 'high')).toThrow(
97
+ 'reasoning_effort is only supported for Claude and Codex models.'
90
98
  );
91
99
  });
92
100
  });
@@ -224,6 +232,57 @@ describe('cli-builder', () => {
224
232
  expect(cmd.resolvedModel).toBe('opus');
225
233
  expect(cmd.args).toContain('opus');
226
234
  });
235
+
236
+ it('should resolve claude-ultra and default to high effort', () => {
237
+ const cmd = buildCliCommand({
238
+ prompt: 'test',
239
+ workFolder: '/tmp',
240
+ model: 'claude-ultra',
241
+ cliPaths: DEFAULT_CLI_PATHS,
242
+ });
243
+
244
+ expect(cmd.args).toContain('--effort');
245
+ expect(cmd.args).toContain('high');
246
+ });
247
+
248
+ it('should build claude command with reasoning_effort using --effort', () => {
249
+ const cmd = buildCliCommand({
250
+ prompt: 'test',
251
+ workFolder: '/tmp',
252
+ model: 'sonnet',
253
+ reasoning_effort: 'medium',
254
+ cliPaths: DEFAULT_CLI_PATHS,
255
+ });
256
+
257
+ expect(cmd.args).toContain('--effort');
258
+ expect(cmd.args).toContain('medium');
259
+ });
260
+
261
+ it('should reject xhigh reasoning_effort for claude', () => {
262
+ expect(() =>
263
+ buildCliCommand({
264
+ prompt: 'test',
265
+ workFolder: '/tmp',
266
+ model: 'sonnet',
267
+ reasoning_effort: 'xhigh',
268
+ cliPaths: DEFAULT_CLI_PATHS,
269
+ })
270
+ ).toThrow('Claude reasoning_effort supports only low, medium, high.');
271
+ });
272
+
273
+ it('should allow overriding reasoning_effort for claude-ultra', () => {
274
+ const cmd = buildCliCommand({
275
+ prompt: 'test',
276
+ workFolder: '/tmp',
277
+ model: 'claude-ultra',
278
+ reasoning_effort: 'low',
279
+ cliPaths: DEFAULT_CLI_PATHS,
280
+ });
281
+
282
+ expect(cmd.args).toContain('--effort');
283
+ expect(cmd.args).toContain('low');
284
+ expect(cmd.args).not.toContain('high');
285
+ });
227
286
  });
228
287
 
229
288
  describe('codex agent', () => {
@@ -280,7 +339,7 @@ describe('cli-builder', () => {
280
339
  });
281
340
 
282
341
  expect(cmd.agent).toBe('codex');
283
- expect(cmd.resolvedModel).toBe('gpt-5.3-codex');
342
+ expect(cmd.resolvedModel).toBe('gpt-5.4');
284
343
  expect(cmd.args).toContain('-c');
285
344
  expect(cmd.args).toContain('model_reasoning_effort=xhigh');
286
345
  });
@@ -301,7 +301,7 @@ describe('Argument Validation Tests', () => {
301
301
  ).rejects.toThrow(/reasoning_effort/i);
302
302
  });
303
303
 
304
- it('should reject reasoning_effort for non-codex models', async () => {
304
+ it('should reject reasoning_effort for unsupported model families', async () => {
305
305
  await setupServer();
306
306
  const handler = handlers.get('callTool')!;
307
307
 
@@ -312,7 +312,7 @@ describe('Argument Validation Tests', () => {
312
312
  arguments: {
313
313
  prompt: 'test',
314
314
  workFolder: '/tmp',
315
- model: 'sonnet',
315
+ model: 'gemini-2.5-pro',
316
316
  reasoning_effort: 'low'
317
317
  }
318
318
  }
@@ -4,11 +4,22 @@ import { resolve as pathResolve, isAbsolute } from 'node:path';
4
4
  // Model alias mappings for user-friendly model names
5
5
  export const MODEL_ALIASES: Record<string, string> = {
6
6
  'claude-ultra': 'opus',
7
- 'codex-ultra': 'gpt-5.3-codex',
7
+ 'codex-ultra': 'gpt-5.4',
8
8
  'gemini-ultra': 'gemini-3.1-pro-preview'
9
9
  };
10
10
 
11
11
  export const ALLOWED_REASONING_EFFORTS = new Set(['low', 'medium', 'high', 'xhigh']);
12
+ const CLAUDE_REASONING_EFFORTS = new Set(['low', 'medium', 'high']);
13
+
14
+ function getAgentForModel(model: string): 'codex' | 'claude' | 'gemini' {
15
+ if (model.startsWith('gpt-')) {
16
+ return 'codex';
17
+ }
18
+ if (model.startsWith('gemini')) {
19
+ return 'gemini';
20
+ }
21
+ return 'claude';
22
+ }
12
23
 
13
24
  /**
14
25
  * Resolves model aliases to their full model names
@@ -38,9 +49,15 @@ export function getReasoningEffort(model: string, rawValue: unknown): string {
38
49
  `Invalid reasoning_effort: ${rawValue}. Allowed values: low, medium, high, xhigh.`
39
50
  );
40
51
  }
41
- if (!model.startsWith('gpt-')) {
52
+ const agent = getAgentForModel(model);
53
+ if (agent === 'gemini') {
54
+ throw new Error(
55
+ 'reasoning_effort is only supported for Claude and Codex models.'
56
+ );
57
+ }
58
+ if (agent === 'claude' && !CLAUDE_REASONING_EFFORTS.has(normalized)) {
42
59
  throw new Error(
43
- 'reasoning_effort is only supported for Codex models (gpt-*).'
60
+ 'Claude reasoning_effort supports only low, medium, high.'
44
61
  );
45
62
  }
46
63
  return normalized;
@@ -117,25 +134,20 @@ export function buildCliCommand(options: BuildCliCommandOptions): CliCommand {
117
134
  // Resolve model
118
135
  const rawModel = options.model || '';
119
136
  const resolvedModel = resolveModelAlias(rawModel);
137
+ const agent = getAgentForModel(resolvedModel);
120
138
 
121
- // Special handling for codex-ultra: default to high reasoning effort if not specified
139
+ // Special handling for ultra aliases: default to higher reasoning if not specified
122
140
  let reasoningEffortArg: string | undefined = options.reasoning_effort;
123
- if (rawModel === 'codex-ultra' && !reasoningEffortArg) {
124
- reasoningEffortArg = 'xhigh';
141
+ if (!reasoningEffortArg) {
142
+ if (rawModel === 'codex-ultra') {
143
+ reasoningEffortArg = 'xhigh';
144
+ } else if (rawModel === 'claude-ultra') {
145
+ reasoningEffortArg = 'high';
146
+ }
125
147
  }
126
148
 
127
149
  const reasoningEffort = getReasoningEffort(resolvedModel, reasoningEffortArg);
128
150
 
129
- // Determine agent
130
- let agent: 'codex' | 'claude' | 'gemini';
131
- if (resolvedModel.startsWith('gpt-')) {
132
- agent = 'codex';
133
- } else if (resolvedModel.startsWith('gemini')) {
134
- agent = 'gemini';
135
- } else {
136
- agent = 'claude';
137
- }
138
-
139
151
  // Build CLI path and args
140
152
  let cliPath: string;
141
153
  let args: string[];
@@ -180,6 +192,10 @@ export function buildCliCommand(options: BuildCliCommandOptions): CliCommand {
180
192
  args.push('-r', options.session_id, '--fork-session');
181
193
  }
182
194
 
195
+ if (reasoningEffort) {
196
+ args.push('--effort', reasoningEffort);
197
+ }
198
+
183
199
  args.push('-p', prompt);
184
200
  if (resolvedModel) {
185
201
  args.push('--model', resolvedModel);
package/src/cli.ts CHANGED
@@ -40,7 +40,7 @@ Options:
40
40
  --prompt Prompt string (mutually exclusive with --prompt_file)
41
41
  --prompt_file Path to a file containing the prompt
42
42
  --session_id Session ID to resume
43
- --reasoning_effort Codex only: low, medium, high, xhigh
43
+ --reasoning_effort Claude/Codex: Claude=low|medium|high, Codex=low|medium|high|xhigh
44
44
  --help Show this help message
45
45
 
46
46
  Raw CLI output goes to stdout. Use cli.run.parse to parse the output:
package/src/server.ts CHANGED
@@ -162,7 +162,7 @@ export class ClaudeCodeServer {
162
162
  **IMPORTANT**: This tool now returns immediately with a PID. Use other tools to check status and get results.
163
163
 
164
164
  **Supported models**:
165
- "claude-ultra", "codex-ultra", "gemini-ultra", "sonnet", "sonnet[1m]", "opus", "opusplan", "haiku", "gpt-5.3-codex", "gpt-5.2-codex", "gpt-5.1-codex-mini", "gpt-5.1-codex-max", "gpt-5.2", "gpt-5.1", "gpt-5.1-codex", "gpt-5-codex", "gpt-5-codex-mini", "gpt-5", "gemini-2.5-pro", "gemini-2.5-flash", "gemini-3.1-pro-preview", "gemini-3-pro-preview", "gemini-3-flash-preview"
165
+ "claude-ultra", "codex-ultra", "gemini-ultra", "sonnet", "sonnet[1m]", "opus", "opusplan", "haiku", "gpt-5.4", "gpt-5.3-codex", "gpt-5.2-codex", "gpt-5.1-codex-mini", "gpt-5.1-codex-max", "gpt-5.2", "gpt-5.1", "gpt-5.1-codex", "gpt-5-codex", "gpt-5-codex-mini", "gpt-5", "gemini-2.5-pro", "gemini-2.5-flash", "gemini-3.1-pro-preview", "gemini-3-pro-preview", "gemini-3-flash-preview"
166
166
 
167
167
  **Prompt input**: You must provide EITHER prompt (string) OR prompt_file (file path), but not both.
168
168
 
@@ -190,11 +190,11 @@ export class ClaudeCodeServer {
190
190
  },
191
191
  model: {
192
192
  type: 'string',
193
- description: 'The model to use. Aliases: "claude-ultra", "codex-ultra" (auto high-reasoning), "gemini-ultra". Standard: "sonnet", "sonnet[1m]", "opus", "opusplan", "haiku", "gpt-5.3-codex", "gpt-5.2-codex", "gpt-5.1-codex-mini", "gpt-5.1", "gemini-2.5-pro", "gemini-3.1-pro-preview", "gemini-3-pro-preview", "gemini-3-flash-preview", etc.',
193
+ description: 'The model to use. Aliases: "claude-ultra" (auto high effort), "codex-ultra" (auto xhigh reasoning), "gemini-ultra". Standard: "sonnet", "sonnet[1m]", "opus", "opusplan", "haiku", "gpt-5.4", "gpt-5.3-codex", "gpt-5.2-codex", "gpt-5.1-codex-mini", "gpt-5.1", "gemini-2.5-pro", "gemini-3.1-pro-preview", "gemini-3-pro-preview", "gemini-3-flash-preview", etc.',
194
194
  },
195
195
  reasoning_effort: {
196
196
  type: 'string',
197
- description: 'Codex only. Sets model_reasoning_effort. Allowed: "low", "medium", "high", "xhigh".',
197
+ description: 'Reasoning control for Claude and Codex. Claude uses --effort with "low", "medium", "high". Codex uses model_reasoning_effort with "low", "medium", "high", "xhigh".',
198
198
  },
199
199
  session_id: {
200
200
  type: 'string',