|
1 | 1 | #!/usr/bin/env bun |
2 | 2 |
|
3 | | -import { describe, test, expect } from "bun:test"; |
| 3 | +import { describe, test, expect, beforeAll, afterAll } from "bun:test"; |
| 4 | +import { writeFileSync, mkdirSync, unlinkSync, rmdirSync } from "fs"; |
4 | 5 | import { parseSdkOptions } from "../src/parse-sdk-options"; |
5 | 6 | import type { ClaudeOptions } from "../src/run-claude"; |
6 | 7 |
|
| 8 | +// Temp dir for MCP config file tests |
| 9 | +const MCP_TEST_DIR = "/tmp/mcp-config-test"; |
| 10 | +const MCP_NOTION_FILE = `${MCP_TEST_DIR}/notion.json`; |
| 11 | +const MCP_SLACK_FILE = `${MCP_TEST_DIR}/slack.json`; |
| 12 | + |
7 | 13 | describe("parseSdkOptions", () => { |
8 | 14 | describe("allowedTools merging", () => { |
9 | 15 | test("should extract allowedTools from claudeArgs", () => { |
@@ -177,6 +183,36 @@ describe("parseSdkOptions", () => { |
177 | 183 | }); |
178 | 184 |
|
179 | 185 | describe("mcp-config merging", () => { |
| 186 | + beforeAll(() => { |
| 187 | + mkdirSync(MCP_TEST_DIR, { recursive: true }); |
| 188 | + writeFileSync( |
| 189 | + MCP_NOTION_FILE, |
| 190 | + JSON.stringify({ |
| 191 | + mcpServers: { |
| 192 | + notion: { command: "npx", args: ["notion-mcp-server"] }, |
| 193 | + }, |
| 194 | + }), |
| 195 | + ); |
| 196 | + writeFileSync( |
| 197 | + MCP_SLACK_FILE, |
| 198 | + JSON.stringify({ |
| 199 | + mcpServers: { |
| 200 | + slack: { command: "npx", args: ["slack-mcp-server"] }, |
| 201 | + }, |
| 202 | + }), |
| 203 | + ); |
| 204 | + }); |
| 205 | + |
| 206 | + afterAll(() => { |
| 207 | + try { |
| 208 | + unlinkSync(MCP_NOTION_FILE); |
| 209 | + unlinkSync(MCP_SLACK_FILE); |
| 210 | + rmdirSync(MCP_TEST_DIR); |
| 211 | + } catch { |
| 212 | + // ignore cleanup errors |
| 213 | + } |
| 214 | + }); |
| 215 | + |
180 | 216 | test("should pass through single mcp-config in extraArgs", () => { |
181 | 217 | const options: ClaudeOptions = { |
182 | 218 | claudeArgs: `--mcp-config '{"mcpServers":{"server1":{"command":"cmd1"}}}'`, |
@@ -221,33 +257,62 @@ describe("parseSdkOptions", () => { |
221 | 257 | expect(mcpConfig.mcpServers).toHaveProperty("server3"); |
222 | 258 | }); |
223 | 259 |
|
224 | | - test("should handle mcp-config file path when no inline JSON exists", () => { |
| 260 | + test("should pass through single file path to CLI as-is", () => { |
| 261 | + // Single mcp-config values are not merged — passed through for CLI to handle |
225 | 262 | const options: ClaudeOptions = { |
226 | | - claudeArgs: `--mcp-config /tmp/user-mcp-config.json`, |
| 263 | + claudeArgs: `--mcp-config ${MCP_NOTION_FILE}`, |
227 | 264 | }; |
228 | 265 |
|
229 | 266 | const result = parseSdkOptions(options); |
230 | 267 |
|
231 | | - expect(result.sdkOptions.extraArgs?.["mcp-config"]).toBe( |
232 | | - "/tmp/user-mcp-config.json", |
233 | | - ); |
| 268 | + expect(result.sdkOptions.extraArgs?.["mcp-config"]).toBe(MCP_NOTION_FILE); |
234 | 269 | }); |
235 | 270 |
|
236 | | - test("should merge inline JSON configs when file path is also present", () => { |
237 | | - // When action provides inline JSON and user provides a file path, |
238 | | - // the inline JSON configs should be merged (file paths cannot be merged at parse time) |
| 271 | + test("should merge inline JSON configs and file path configs together", () => { |
| 272 | + // This is the exact scenario from issue #1191: action provides inline JSON |
| 273 | + // for github_comment, user provides a file path for their MCP server |
239 | 274 | const options: ClaudeOptions = { |
240 | | - claudeArgs: `--mcp-config '{"mcpServers":{"github_comment":{"command":"node"}}}' --mcp-config '{"mcpServers":{"github_ci":{"command":"node"}}}' --mcp-config /tmp/user-config.json`, |
| 275 | + claudeArgs: `--mcp-config '{"mcpServers":{"github_comment":{"command":"node"}}}' --mcp-config '{"mcpServers":{"github_ci":{"command":"node"}}}' --mcp-config ${MCP_NOTION_FILE}`, |
241 | 276 | }; |
242 | 277 |
|
243 | 278 | const result = parseSdkOptions(options); |
244 | 279 |
|
245 | | - // The inline JSON configs should be merged |
246 | 280 | const mcpConfig = JSON.parse( |
247 | 281 | result.sdkOptions.extraArgs?.["mcp-config"] as string, |
248 | 282 | ); |
| 283 | + // All servers should be present — including the file-path one |
249 | 284 | expect(mcpConfig.mcpServers).toHaveProperty("github_comment"); |
250 | 285 | expect(mcpConfig.mcpServers).toHaveProperty("github_ci"); |
| 286 | + expect(mcpConfig.mcpServers).toHaveProperty("notion"); |
| 287 | + expect(mcpConfig.mcpServers.notion.command).toBe("npx"); |
| 288 | + }); |
| 289 | + |
| 290 | + test("should merge multiple file paths", () => { |
| 291 | + const options: ClaudeOptions = { |
| 292 | + claudeArgs: `--mcp-config ${MCP_NOTION_FILE} --mcp-config ${MCP_SLACK_FILE}`, |
| 293 | + }; |
| 294 | + |
| 295 | + const result = parseSdkOptions(options); |
| 296 | + |
| 297 | + const mcpConfig = JSON.parse( |
| 298 | + result.sdkOptions.extraArgs?.["mcp-config"] as string, |
| 299 | + ); |
| 300 | + expect(mcpConfig.mcpServers).toHaveProperty("notion"); |
| 301 | + expect(mcpConfig.mcpServers).toHaveProperty("slack"); |
| 302 | + }); |
| 303 | + |
| 304 | + test("should warn and continue when file path does not exist", () => { |
| 305 | + const options: ClaudeOptions = { |
| 306 | + claudeArgs: `--mcp-config '{"mcpServers":{"github_comment":{"command":"node"}}}' --mcp-config /tmp/nonexistent-mcp-config.json`, |
| 307 | + }; |
| 308 | + |
| 309 | + const result = parseSdkOptions(options); |
| 310 | + |
| 311 | + // Should still have the inline config, file path failure is non-fatal |
| 312 | + const mcpConfig = JSON.parse( |
| 313 | + result.sdkOptions.extraArgs?.["mcp-config"] as string, |
| 314 | + ); |
| 315 | + expect(mcpConfig.mcpServers).toHaveProperty("github_comment"); |
251 | 316 | }); |
252 | 317 |
|
253 | 318 | test("should handle mcp-config with other flags", () => { |
|
0 commit comments