feat(prd-1): implement M6 - MCP server platform routing

- Add platform detection to prepareMcpConfig() via detectPlatform()
- Skip GitHub public MCP server (ghcr.io/github/github-mcp-server) on Forgejo
- Skip checkActionsReadPermission on Forgejo (workflow token has full access)
- Reuse existing github-actions-server.ts for both platforms (no duplication)
- Add 6 Forgejo-specific tests to install-mcp-server.test.ts
- Updated PRD checkboxes for M6

Progress: 67% complete (6/9 milestones) - M7 now unblocked

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Vlad MocanuandClaude Opus 4.6 committed 2026-02-25 17:57:35 +02:00
1 parent 0802ac18eb
commit ce1459fe7f
3 files changed
+208 -17

No files matched your search

+174
View File
@@ -313,4 +313,178 @@ describe("prepareMcpConfig", () => {
const parsed = JSON.parse(result);
expect(parsed.mcpServers.github_ci).not.toBeDefined();
});
describe("Forgejo platform routing", () => {
let savedPlatform: string | undefined;
let savedForgejo: string | undefined;
let savedServerUrl: string | undefined;
beforeEach(() => {
savedPlatform = process.env.PLATFORM;
savedForgejo = process.env.FORGEJO;
savedServerUrl = process.env.GITHUB_SERVER_URL;
// Set platform to forgejo for all tests in this block
process.env.PLATFORM = "forgejo";
});
afterEach(() => {
if (savedPlatform !== undefined) {
process.env.PLATFORM = savedPlatform;
} else {
delete process.env.PLATFORM;
}
if (savedForgejo !== undefined) {
process.env.FORGEJO = savedForgejo;
} else {
delete process.env.FORGEJO;
}
if (savedServerUrl !== undefined) {
process.env.GITHUB_SERVER_URL = savedServerUrl;
} else {
delete process.env.GITHUB_SERVER_URL;
}
});
test("should skip GitHub public MCP server on Forgejo", async () => {
const result = await prepareMcpConfig({
githubToken: "test-token",
owner: "test-owner",
repo: "test-repo",
branch: "test-branch",
baseBranch: "main",
allowedTools: [
"mcp__github__create_issue",
"mcp__github__create_pr",
],
mode: "tag",
context: mockContext,
});
const parsed = JSON.parse(result);
// The Docker-based GitHub MCP server should NOT be included on Forgejo
expect(parsed.mcpServers.github).not.toBeDefined();
});
test("should include CI server on Forgejo without permission check", async () => {
process.env.DEFAULT_WORKFLOW_TOKEN = "forgejo-workflow-token";
// Simulate a 403 from the permission check — on Forgejo this should be
// skipped entirely, so the CI server is still included.
fetchSpy.mockResolvedValue(
new Response(
JSON.stringify({
message: "Resource not accessible by integration",
}),
{ status: 403 },
),
);
const result = await prepareMcpConfig({
githubToken: "test-token",
owner: "test-owner",
repo: "test-repo",
branch: "test-branch",
baseBranch: "main",
allowedTools: [],
mode: "tag",
context: mockPRContext,
});
const parsed = JSON.parse(result);
// CI server should be included on Forgejo even though the permission
// check would fail — the check is skipped entirely.
expect(parsed.mcpServers.github_ci).toBeDefined();
expect(parsed.mcpServers.github_ci.env.GITHUB_TOKEN).toBe(
"forgejo-workflow-token",
);
delete process.env.DEFAULT_WORKFLOW_TOKEN;
});
test("should not call checkActionsReadPermission on Forgejo", async () => {
process.env.DEFAULT_WORKFLOW_TOKEN = "forgejo-workflow-token";
fetchSpy.mockClear();
await prepareMcpConfig({
githubToken: "test-token",
owner: "test-owner",
repo: "test-repo",
branch: "test-branch",
baseBranch: "main",
allowedTools: [],
mode: "tag",
context: mockPRContext,
});
// fetch should not have been called — the permission check is skipped
expect(fetchSpy).not.toHaveBeenCalled();
delete process.env.DEFAULT_WORKFLOW_TOKEN;
});
test("should keep server names as github_* on Forgejo", async () => {
process.env.DEFAULT_WORKFLOW_TOKEN = "forgejo-workflow-token";
const result = await prepareMcpConfig({
githubToken: "test-token",
owner: "test-owner",
repo: "test-repo",
branch: "test-branch",
baseBranch: "main",
allowedTools: [],
mode: "tag",
context: {
...mockPRContext,
inputs: { ...mockPRContext.inputs, useCommitSigning: true },
},
});
const parsed = JSON.parse(result);
// Server names must stay as github_* to match hardcoded tool names
expect(parsed.mcpServers.github_comment).toBeDefined();
expect(parsed.mcpServers.github_file_ops).toBeDefined();
expect(parsed.mcpServers.github_ci).toBeDefined();
delete process.env.DEFAULT_WORKFLOW_TOKEN;
});
test("should include comment server on Forgejo in tag mode", async () => {
const result = await prepareMcpConfig({
githubToken: "test-token",
owner: "test-owner",
repo: "test-repo",
branch: "test-branch",
baseBranch: "main",
allowedTools: [],
mode: "tag",
context: mockContext,
});
const parsed = JSON.parse(result);
expect(parsed.mcpServers.github_comment).toBeDefined();
expect(parsed.mcpServers.github_comment.env.GITHUB_TOKEN).toBe(
"test-token",
);
});
test("should include inline comment server on Forgejo for PRs", async () => {
const result = await prepareMcpConfig({
githubToken: "test-token",
owner: "test-owner",
repo: "test-repo",
branch: "test-branch",
baseBranch: "main",
allowedTools: [
"mcp__github_inline_comment__create_inline_comment",
],
mode: "tag",
context: mockPRContext,
});
const parsed = JSON.parse(result);
expect(parsed.mcpServers.github_inline_comment).toBeDefined();
expect(
parsed.mcpServers.github_inline_comment.env.GITHUB_TOKEN,
).toBe("test-token");
});
});
});