mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-22 22:44:13 +08:00
feat: add mcp_config input that merges with existing mcp server (#96)
* feat: add mcp_config input that merges with existing mcp server - Add mcp_config input parameter to action.yml - Modify prepareMcpConfig() to accept and merge additional config - Provided config overrides built-in servers in case of naming collisions - Pass MCP_CONFIG environment variable from action to prepare step Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com> * refactor: improve MCP config validation and merging logic - Add JSON validation to ensure parsed config is an object - Simplify merge logic with explicit mcpServers merging - Enhance error logging with config preview for debugging Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com> * refactor: improve MCP config logging per review feedback - Remove configPreview from error logging to avoid cluttering output - Add informational log when merging MCP server configurations - Simplify error message for failed config parsing Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com> * test: add comprehensive unit tests for prepareMcpConfig Add tests covering: - Basic functionality with no additional config - Valid JSON merging scenarios - Invalid JSON handling - Empty/null config handling - Server name collision scenarios - Complex nested configurations - Environment variable handling Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com> * docs: add mcp_config example with sequential-thinking server - Add mcp_config to inputs table - Add example section showing how to use mcp_config with sequential-thinking MCP server - Include clear explanation that custom servers override built-in servers Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com> * readme --------- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: ashwin-ant <ashwin-ant@users.noreply.github.com>
This commit is contained in:
56
README.md
56
README.md
@@ -82,6 +82,7 @@ jobs:
|
|||||||
| `allowed_tools` | Additional tools for Claude to use (the base GitHub tools will always be included) | No | "" |
|
| `allowed_tools` | Additional tools for Claude to use (the base GitHub tools will always be included) | No | "" |
|
||||||
| `disallowed_tools` | Tools that Claude should never use | No | "" |
|
| `disallowed_tools` | Tools that Claude should never use | No | "" |
|
||||||
| `custom_instructions` | Additional custom instructions to include in the prompt for Claude | No | "" |
|
| `custom_instructions` | Additional custom instructions to include in the prompt for Claude | No | "" |
|
||||||
|
| `mcp_config` | Additional MCP configuration (JSON string) that merges with the built-in GitHub MCP servers | No | "" |
|
||||||
| `assignee_trigger` | The assignee username that triggers the action (e.g. @claude). Only used for issue assignment | No | - |
|
| `assignee_trigger` | The assignee username that triggers the action (e.g. @claude). Only used for issue assignment | No | - |
|
||||||
| `trigger_phrase` | The trigger phrase to look for in comments, issue/PR bodies, and issue titles | No | `@claude` |
|
| `trigger_phrase` | The trigger phrase to look for in comments, issue/PR bodies, and issue titles | No | `@claude` |
|
||||||
|
|
||||||
@@ -89,6 +90,61 @@ jobs:
|
|||||||
|
|
||||||
> **Note**: This action is currently in beta. Features and APIs may change as we continue to improve the integration.
|
> **Note**: This action is currently in beta. Features and APIs may change as we continue to improve the integration.
|
||||||
|
|
||||||
|
### Using Custom MCP Configuration
|
||||||
|
|
||||||
|
The `mcp_config` input allows you to add custom MCP (Model Context Protocol) servers to extend Claude's capabilities. These servers merge with the built-in GitHub MCP servers.
|
||||||
|
|
||||||
|
#### Basic Example: Adding a Sequential Thinking Server
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- uses: anthropics/claude-code-action@beta
|
||||||
|
with:
|
||||||
|
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||||
|
mcp_config: |
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"sequential-thinking": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": [
|
||||||
|
"-y",
|
||||||
|
"@modelcontextprotocol/server-sequential-thinking"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
allowed_tools: "mcp__sequential-thinking__sequentialthinking" # Important: Each MCP tool from your server must be listed here, comma-separated
|
||||||
|
# ... other inputs
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Passing Secrets to MCP Servers
|
||||||
|
|
||||||
|
For MCP servers that require sensitive information like API keys or tokens, use GitHub Secrets in the environment variables:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- uses: anthropics/claude-code-action@beta
|
||||||
|
with:
|
||||||
|
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||||
|
mcp_config: |
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"custom-api-server": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": ["-y", "@example/api-server"],
|
||||||
|
"env": {
|
||||||
|
"API_KEY": "${{ secrets.CUSTOM_API_KEY }}",
|
||||||
|
"BASE_URL": "https://api.example.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# ... other inputs
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important**:
|
||||||
|
|
||||||
|
- Always use GitHub Secrets (`${{ secrets.SECRET_NAME }}`) for sensitive values like API keys, tokens, or passwords. Never hardcode secrets directly in the workflow file.
|
||||||
|
- Your custom servers will override any built-in servers with the same name.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
### Ways to Tag @claude
|
### Ways to Tag @claude
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ inputs:
|
|||||||
description: "Direct instruction for Claude (bypasses normal trigger detection)"
|
description: "Direct instruction for Claude (bypasses normal trigger detection)"
|
||||||
required: false
|
required: false
|
||||||
default: ""
|
default: ""
|
||||||
|
mcp_config:
|
||||||
|
description: "Additional MCP configuration (JSON string) that merges with the built-in GitHub MCP servers"
|
||||||
|
required: false
|
||||||
|
default: ""
|
||||||
|
|
||||||
# Auth configuration
|
# Auth configuration
|
||||||
anthropic_api_key:
|
anthropic_api_key:
|
||||||
@@ -92,6 +96,7 @@ runs:
|
|||||||
ALLOWED_TOOLS: ${{ inputs.allowed_tools }}
|
ALLOWED_TOOLS: ${{ inputs.allowed_tools }}
|
||||||
CUSTOM_INSTRUCTIONS: ${{ inputs.custom_instructions }}
|
CUSTOM_INSTRUCTIONS: ${{ inputs.custom_instructions }}
|
||||||
DIRECT_PROMPT: ${{ inputs.direct_prompt }}
|
DIRECT_PROMPT: ${{ inputs.direct_prompt }}
|
||||||
|
MCP_CONFIG: ${{ inputs.mcp_config }}
|
||||||
OVERRIDE_GITHUB_TOKEN: ${{ inputs.github_token }}
|
OVERRIDE_GITHUB_TOKEN: ${{ inputs.github_token }}
|
||||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||||
|
|
||||||
|
|||||||
@@ -84,11 +84,13 @@ async function run() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Step 11: Get MCP configuration
|
// Step 11: Get MCP configuration
|
||||||
|
const additionalMcpConfig = process.env.MCP_CONFIG || "";
|
||||||
const mcpConfig = await prepareMcpConfig(
|
const mcpConfig = await prepareMcpConfig(
|
||||||
githubToken,
|
githubToken,
|
||||||
context.repository.owner,
|
context.repository.owner,
|
||||||
context.repository.repo,
|
context.repository.repo,
|
||||||
branchInfo.currentBranch,
|
branchInfo.currentBranch,
|
||||||
|
additionalMcpConfig,
|
||||||
);
|
);
|
||||||
core.setOutput("mcp_config", mcpConfig);
|
core.setOutput("mcp_config", mcpConfig);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ export async function prepareMcpConfig(
|
|||||||
owner: string,
|
owner: string,
|
||||||
repo: string,
|
repo: string,
|
||||||
branch: string,
|
branch: string,
|
||||||
|
additionalMcpConfig?: string,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
try {
|
try {
|
||||||
const mcpConfig = {
|
const baseMcpConfig = {
|
||||||
mcpServers: {
|
mcpServers: {
|
||||||
github: {
|
github: {
|
||||||
command: "docker",
|
command: "docker",
|
||||||
@@ -40,7 +41,39 @@ export async function prepareMcpConfig(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return JSON.stringify(mcpConfig, null, 2);
|
// Merge with additional MCP config if provided
|
||||||
|
if (additionalMcpConfig && additionalMcpConfig.trim()) {
|
||||||
|
try {
|
||||||
|
const additionalConfig = JSON.parse(additionalMcpConfig);
|
||||||
|
|
||||||
|
// Validate that parsed JSON is an object
|
||||||
|
if (typeof additionalConfig !== "object" || additionalConfig === null) {
|
||||||
|
throw new Error("MCP config must be a valid JSON object");
|
||||||
|
}
|
||||||
|
|
||||||
|
core.info(
|
||||||
|
"Merging additional MCP server configuration with built-in servers",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Merge configurations with user config overriding built-in servers
|
||||||
|
const mergedConfig = {
|
||||||
|
...baseMcpConfig,
|
||||||
|
...additionalConfig,
|
||||||
|
mcpServers: {
|
||||||
|
...baseMcpConfig.mcpServers,
|
||||||
|
...additionalConfig.mcpServers,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return JSON.stringify(mergedConfig, null, 2);
|
||||||
|
} catch (parseError) {
|
||||||
|
core.warning(
|
||||||
|
`Failed to parse additional MCP config: ${parseError}. Using base config only.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify(baseMcpConfig, null, 2);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
core.setFailed(`Install MCP server failed with error: ${error}`);
|
core.setFailed(`Install MCP server failed with error: ${error}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
|||||||
344
test/install-mcp-server.test.ts
Normal file
344
test/install-mcp-server.test.ts
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
import { describe, test, expect, beforeEach, afterEach, spyOn } from "bun:test";
|
||||||
|
import { prepareMcpConfig } from "../src/mcp/install-mcp-server";
|
||||||
|
import * as core from "@actions/core";
|
||||||
|
|
||||||
|
describe("prepareMcpConfig", () => {
|
||||||
|
let consoleInfoSpy: any;
|
||||||
|
let consoleWarningSpy: any;
|
||||||
|
let setFailedSpy: any;
|
||||||
|
let processExitSpy: any;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
consoleInfoSpy = spyOn(core, "info").mockImplementation(() => {});
|
||||||
|
consoleWarningSpy = spyOn(core, "warning").mockImplementation(() => {});
|
||||||
|
setFailedSpy = spyOn(core, "setFailed").mockImplementation(() => {});
|
||||||
|
processExitSpy = spyOn(process, "exit").mockImplementation(() => {
|
||||||
|
throw new Error("Process exit");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
consoleInfoSpy.mockRestore();
|
||||||
|
consoleWarningSpy.mockRestore();
|
||||||
|
setFailedSpy.mockRestore();
|
||||||
|
processExitSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return base config when no additional config is provided", async () => {
|
||||||
|
const result = await prepareMcpConfig(
|
||||||
|
"test-token",
|
||||||
|
"test-owner",
|
||||||
|
"test-repo",
|
||||||
|
"test-branch",
|
||||||
|
);
|
||||||
|
|
||||||
|
const parsed = JSON.parse(result);
|
||||||
|
expect(parsed.mcpServers).toBeDefined();
|
||||||
|
expect(parsed.mcpServers.github).toBeDefined();
|
||||||
|
expect(parsed.mcpServers.github_file_ops).toBeDefined();
|
||||||
|
expect(parsed.mcpServers.github.env.GITHUB_PERSONAL_ACCESS_TOKEN).toBe(
|
||||||
|
"test-token",
|
||||||
|
);
|
||||||
|
expect(parsed.mcpServers.github_file_ops.env.GITHUB_TOKEN).toBe(
|
||||||
|
"test-token",
|
||||||
|
);
|
||||||
|
expect(parsed.mcpServers.github_file_ops.env.REPO_OWNER).toBe("test-owner");
|
||||||
|
expect(parsed.mcpServers.github_file_ops.env.REPO_NAME).toBe("test-repo");
|
||||||
|
expect(parsed.mcpServers.github_file_ops.env.BRANCH_NAME).toBe(
|
||||||
|
"test-branch",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return base config when additional config is empty string", async () => {
|
||||||
|
const result = await prepareMcpConfig(
|
||||||
|
"test-token",
|
||||||
|
"test-owner",
|
||||||
|
"test-repo",
|
||||||
|
"test-branch",
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
|
||||||
|
const parsed = JSON.parse(result);
|
||||||
|
expect(parsed.mcpServers).toBeDefined();
|
||||||
|
expect(parsed.mcpServers.github).toBeDefined();
|
||||||
|
expect(parsed.mcpServers.github_file_ops).toBeDefined();
|
||||||
|
expect(consoleWarningSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return base config when additional config is whitespace only", async () => {
|
||||||
|
const result = await prepareMcpConfig(
|
||||||
|
"test-token",
|
||||||
|
"test-owner",
|
||||||
|
"test-repo",
|
||||||
|
"test-branch",
|
||||||
|
" \n\t ",
|
||||||
|
);
|
||||||
|
|
||||||
|
const parsed = JSON.parse(result);
|
||||||
|
expect(parsed.mcpServers).toBeDefined();
|
||||||
|
expect(parsed.mcpServers.github).toBeDefined();
|
||||||
|
expect(parsed.mcpServers.github_file_ops).toBeDefined();
|
||||||
|
expect(consoleWarningSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should merge valid additional config with base config", async () => {
|
||||||
|
const additionalConfig = JSON.stringify({
|
||||||
|
mcpServers: {
|
||||||
|
custom_server: {
|
||||||
|
command: "custom-command",
|
||||||
|
args: ["arg1", "arg2"],
|
||||||
|
env: {
|
||||||
|
CUSTOM_ENV: "custom-value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await prepareMcpConfig(
|
||||||
|
"test-token",
|
||||||
|
"test-owner",
|
||||||
|
"test-repo",
|
||||||
|
"test-branch",
|
||||||
|
additionalConfig,
|
||||||
|
);
|
||||||
|
|
||||||
|
const parsed = JSON.parse(result);
|
||||||
|
expect(consoleInfoSpy).toHaveBeenCalledWith(
|
||||||
|
"Merging additional MCP server configuration with built-in servers",
|
||||||
|
);
|
||||||
|
expect(parsed.mcpServers.github).toBeDefined();
|
||||||
|
expect(parsed.mcpServers.github_file_ops).toBeDefined();
|
||||||
|
expect(parsed.mcpServers.custom_server).toBeDefined();
|
||||||
|
expect(parsed.mcpServers.custom_server.command).toBe("custom-command");
|
||||||
|
expect(parsed.mcpServers.custom_server.args).toEqual(["arg1", "arg2"]);
|
||||||
|
expect(parsed.mcpServers.custom_server.env.CUSTOM_ENV).toBe("custom-value");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should override built-in servers when additional config has same server names", async () => {
|
||||||
|
const additionalConfig = JSON.stringify({
|
||||||
|
mcpServers: {
|
||||||
|
github: {
|
||||||
|
command: "overridden-command",
|
||||||
|
args: ["overridden-arg"],
|
||||||
|
env: {
|
||||||
|
OVERRIDDEN_ENV: "overridden-value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await prepareMcpConfig(
|
||||||
|
"test-token",
|
||||||
|
"test-owner",
|
||||||
|
"test-repo",
|
||||||
|
"test-branch",
|
||||||
|
additionalConfig,
|
||||||
|
);
|
||||||
|
|
||||||
|
const parsed = JSON.parse(result);
|
||||||
|
expect(consoleInfoSpy).toHaveBeenCalledWith(
|
||||||
|
"Merging additional MCP server configuration with built-in servers",
|
||||||
|
);
|
||||||
|
expect(parsed.mcpServers.github.command).toBe("overridden-command");
|
||||||
|
expect(parsed.mcpServers.github.args).toEqual(["overridden-arg"]);
|
||||||
|
expect(parsed.mcpServers.github.env.OVERRIDDEN_ENV).toBe(
|
||||||
|
"overridden-value",
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
parsed.mcpServers.github.env.GITHUB_PERSONAL_ACCESS_TOKEN,
|
||||||
|
).toBeUndefined();
|
||||||
|
expect(parsed.mcpServers.github_file_ops).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should merge additional root-level properties", async () => {
|
||||||
|
const additionalConfig = JSON.stringify({
|
||||||
|
customProperty: "custom-value",
|
||||||
|
anotherProperty: {
|
||||||
|
nested: "value",
|
||||||
|
},
|
||||||
|
mcpServers: {
|
||||||
|
custom_server: {
|
||||||
|
command: "custom",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await prepareMcpConfig(
|
||||||
|
"test-token",
|
||||||
|
"test-owner",
|
||||||
|
"test-repo",
|
||||||
|
"test-branch",
|
||||||
|
additionalConfig,
|
||||||
|
);
|
||||||
|
|
||||||
|
const parsed = JSON.parse(result);
|
||||||
|
expect(parsed.customProperty).toBe("custom-value");
|
||||||
|
expect(parsed.anotherProperty).toEqual({ nested: "value" });
|
||||||
|
expect(parsed.mcpServers.github).toBeDefined();
|
||||||
|
expect(parsed.mcpServers.custom_server).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle invalid JSON gracefully", async () => {
|
||||||
|
const invalidJson = "{ invalid json }";
|
||||||
|
|
||||||
|
const result = await prepareMcpConfig(
|
||||||
|
"test-token",
|
||||||
|
"test-owner",
|
||||||
|
"test-repo",
|
||||||
|
"test-branch",
|
||||||
|
invalidJson,
|
||||||
|
);
|
||||||
|
|
||||||
|
const parsed = JSON.parse(result);
|
||||||
|
expect(consoleWarningSpy).toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining("Failed to parse additional MCP config:"),
|
||||||
|
);
|
||||||
|
expect(parsed.mcpServers.github).toBeDefined();
|
||||||
|
expect(parsed.mcpServers.github_file_ops).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle non-object JSON values", async () => {
|
||||||
|
const nonObjectJson = JSON.stringify("string value");
|
||||||
|
|
||||||
|
const result = await prepareMcpConfig(
|
||||||
|
"test-token",
|
||||||
|
"test-owner",
|
||||||
|
"test-repo",
|
||||||
|
"test-branch",
|
||||||
|
nonObjectJson,
|
||||||
|
);
|
||||||
|
|
||||||
|
const parsed = JSON.parse(result);
|
||||||
|
expect(consoleWarningSpy).toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining("Failed to parse additional MCP config:"),
|
||||||
|
);
|
||||||
|
expect(consoleWarningSpy).toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining("MCP config must be a valid JSON object"),
|
||||||
|
);
|
||||||
|
expect(parsed.mcpServers.github).toBeDefined();
|
||||||
|
expect(parsed.mcpServers.github_file_ops).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle null JSON value", async () => {
|
||||||
|
const nullJson = JSON.stringify(null);
|
||||||
|
|
||||||
|
const result = await prepareMcpConfig(
|
||||||
|
"test-token",
|
||||||
|
"test-owner",
|
||||||
|
"test-repo",
|
||||||
|
"test-branch",
|
||||||
|
nullJson,
|
||||||
|
);
|
||||||
|
|
||||||
|
const parsed = JSON.parse(result);
|
||||||
|
expect(consoleWarningSpy).toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining("Failed to parse additional MCP config:"),
|
||||||
|
);
|
||||||
|
expect(consoleWarningSpy).toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining("MCP config must be a valid JSON object"),
|
||||||
|
);
|
||||||
|
expect(parsed.mcpServers.github).toBeDefined();
|
||||||
|
expect(parsed.mcpServers.github_file_ops).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle array JSON value", async () => {
|
||||||
|
const arrayJson = JSON.stringify([1, 2, 3]);
|
||||||
|
|
||||||
|
const result = await prepareMcpConfig(
|
||||||
|
"test-token",
|
||||||
|
"test-owner",
|
||||||
|
"test-repo",
|
||||||
|
"test-branch",
|
||||||
|
arrayJson,
|
||||||
|
);
|
||||||
|
|
||||||
|
const parsed = JSON.parse(result);
|
||||||
|
// Arrays are objects in JavaScript, so they pass the object check
|
||||||
|
// But they'll fail when trying to spread or access mcpServers property
|
||||||
|
expect(consoleInfoSpy).toHaveBeenCalledWith(
|
||||||
|
"Merging additional MCP server configuration with built-in servers",
|
||||||
|
);
|
||||||
|
expect(parsed.mcpServers.github).toBeDefined();
|
||||||
|
expect(parsed.mcpServers.github_file_ops).toBeDefined();
|
||||||
|
// The array will be spread into the config (0: 1, 1: 2, 2: 3)
|
||||||
|
expect(parsed[0]).toBe(1);
|
||||||
|
expect(parsed[1]).toBe(2);
|
||||||
|
expect(parsed[2]).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should merge complex nested configurations", async () => {
|
||||||
|
const additionalConfig = JSON.stringify({
|
||||||
|
mcpServers: {
|
||||||
|
server1: {
|
||||||
|
command: "cmd1",
|
||||||
|
env: { KEY1: "value1" },
|
||||||
|
},
|
||||||
|
server2: {
|
||||||
|
command: "cmd2",
|
||||||
|
env: { KEY2: "value2" },
|
||||||
|
},
|
||||||
|
github_file_ops: {
|
||||||
|
command: "overridden",
|
||||||
|
env: { CUSTOM: "value" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
otherConfig: {
|
||||||
|
nested: {
|
||||||
|
deeply: "value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await prepareMcpConfig(
|
||||||
|
"test-token",
|
||||||
|
"test-owner",
|
||||||
|
"test-repo",
|
||||||
|
"test-branch",
|
||||||
|
additionalConfig,
|
||||||
|
);
|
||||||
|
|
||||||
|
const parsed = JSON.parse(result);
|
||||||
|
expect(parsed.mcpServers.server1).toBeDefined();
|
||||||
|
expect(parsed.mcpServers.server2).toBeDefined();
|
||||||
|
expect(parsed.mcpServers.github).toBeDefined();
|
||||||
|
expect(parsed.mcpServers.github_file_ops.command).toBe("overridden");
|
||||||
|
expect(parsed.mcpServers.github_file_ops.env.CUSTOM).toBe("value");
|
||||||
|
expect(parsed.otherConfig.nested.deeply).toBe("value");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should preserve GITHUB_ACTION_PATH in file_ops server args", async () => {
|
||||||
|
const oldEnv = process.env.GITHUB_ACTION_PATH;
|
||||||
|
process.env.GITHUB_ACTION_PATH = "/test/action/path";
|
||||||
|
|
||||||
|
const result = await prepareMcpConfig(
|
||||||
|
"test-token",
|
||||||
|
"test-owner",
|
||||||
|
"test-repo",
|
||||||
|
"test-branch",
|
||||||
|
);
|
||||||
|
|
||||||
|
const parsed = JSON.parse(result);
|
||||||
|
expect(parsed.mcpServers.github_file_ops.args[1]).toBe(
|
||||||
|
"/test/action/path/src/mcp/github-file-ops-server.ts",
|
||||||
|
);
|
||||||
|
|
||||||
|
process.env.GITHUB_ACTION_PATH = oldEnv;
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should use process.cwd() when GITHUB_WORKSPACE is not set", async () => {
|
||||||
|
const oldEnv = process.env.GITHUB_WORKSPACE;
|
||||||
|
delete process.env.GITHUB_WORKSPACE;
|
||||||
|
|
||||||
|
const result = await prepareMcpConfig(
|
||||||
|
"test-token",
|
||||||
|
"test-owner",
|
||||||
|
"test-repo",
|
||||||
|
"test-branch",
|
||||||
|
);
|
||||||
|
|
||||||
|
const parsed = JSON.parse(result);
|
||||||
|
expect(parsed.mcpServers.github_file_ops.env.REPO_DIR).toBe(process.cwd());
|
||||||
|
|
||||||
|
process.env.GITHUB_WORKSPACE = oldEnv;
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user