diff --git a/src/create-prompt/index.ts b/src/create-prompt/index.ts index 93bca54..79ad7e3 100644 --- a/src/create-prompt/index.ts +++ b/src/create-prompt/index.ts @@ -31,6 +31,7 @@ const BASE_ALLOWED_TOOLS = [ "Write", "mcp__github_file_ops__commit_files", "mcp__github_file_ops__delete_files", + "mcp__github_file_ops__update_claude_comment", ]; const DISALLOWED_TOOLS = ["WebSearch", "WebFetch"]; @@ -40,15 +41,6 @@ export function buildAllowedToolsString( ): string { let baseTools = [...BASE_ALLOWED_TOOLS]; - // Add the appropriate comment tool based on event type - if (eventData.eventName === "pull_request_review_comment") { - // For inline PR review comments, only use PR comment tool - baseTools.push("mcp__github__update_pull_request_comment"); - } else { - // For all other events (issue comments, PR reviews, issues), use issue comment tool - baseTools.push("mcp__github__update_issue_comment"); - } - let allAllowedTools = baseTools.join(","); if (customAllowedTools) { allAllowedTools = `${allAllowedTools},${customAllowedTools}`; @@ -447,33 +439,15 @@ ${sanitizeContent(context.directPrompt)} ` : "" } -${ - eventData.eventName === "pull_request_review_comment" - ? ` -IMPORTANT: For this inline PR review comment, you have been provided with ONLY the mcp__github__update_pull_request_comment tool to update this specific review comment. +${` +IMPORTANT: You have been provided with the mcp__github_file_ops__update_claude_comment tool to update your comment. This tool automatically handles both issue and PR comments. -Tool usage example for mcp__github__update_pull_request_comment: +Tool usage example for mcp__github_file_ops__update_claude_comment: { - "owner": "${context.repository.split("/")[0]}", - "repo": "${context.repository.split("/")[1]}", - "commentId": ${eventData.commentId || context.claudeCommentId}, "body": "Your comment text here" } -All four parameters (owner, repo, commentId, body) are required. -` - : ` -IMPORTANT: For this event type, you have been provided with ONLY the mcp__github__update_issue_comment tool to update comments. - -Tool usage example for mcp__github__update_issue_comment: -{ - "owner": "${context.repository.split("/")[0]}", - "repo": "${context.repository.split("/")[1]}", - "commentId": ${context.claudeCommentId}, - "body": "Your comment text here" -} -All four parameters (owner, repo, commentId, body) are required. -` -} +Only the body parameter is required - the tool automatically knows which comment to update. +`} Your task is to analyze the context, understand the request, and provide helpful responses and/or implement code changes as needed. @@ -487,7 +461,7 @@ Follow these steps: 1. Create a Todo List: - Use your GitHub comment to maintain a detailed task list based on the request. - Format todos as a checklist (- [ ] for incomplete, - [x] for complete). - - Update the comment using ${eventData.eventName === "pull_request_review_comment" ? "mcp__github__update_pull_request_comment" : "mcp__github__update_issue_comment"} with each task completion. + - Update the comment using mcp__github_file_ops__update_claude_comment with each task completion. 2. Gather Context: - Analyze the pre-fetched data provided above. @@ -517,11 +491,11 @@ ${context.directPrompt ? ` - DIRECT INSTRUCTION: A direct instruction was prov - Look for bugs, security issues, performance problems, and other issues - Suggest improvements for readability and maintainability - Check for best practices and coding standards - - Reference specific code sections with file paths and line numbers${eventData.isPR ? "\n - AFTER reading files and analyzing code, you MUST call mcp__github__update_issue_comment to post your review" : ""} + - Reference specific code sections with file paths and line numbers${eventData.isPR ? "\n - AFTER reading files and analyzing code, you MUST call mcp__github_file_ops__update_claude_comment to post your review" : ""} - Formulate a concise, technical, and helpful response based on the context. - Reference specific code with inline formatting or code blocks. - Include relevant file paths and line numbers when applicable. - - ${eventData.isPR ? "IMPORTANT: Submit your review feedback by updating the Claude comment. This will be displayed as your PR review." : "Remember that this feedback must be posted to the GitHub comment."} + - ${eventData.isPR ? "IMPORTANT: Submit your review feedback by updating the Claude comment using mcp__github_file_ops__update_claude_comment. This will be displayed as your PR review." : "Remember that this feedback must be posted to the GitHub comment using mcp__github_file_ops__update_claude_comment."} B. For Straightforward Changes: - Use file system tools to make the change locally. @@ -576,8 +550,8 @@ ${context.directPrompt ? ` - DIRECT INSTRUCTION: A direct instruction was prov Important Notes: - All communication must happen through GitHub PR comments. -- Never create new comments. Only update the existing comment using ${eventData.eventName === "pull_request_review_comment" ? "mcp__github__update_pull_request_comment" : "mcp__github__update_issue_comment"} with comment_id: ${context.claudeCommentId}. -- This includes ALL responses: code reviews, answers to questions, progress updates, and final results.${eventData.isPR ? "\n- PR CRITICAL: After reading files and forming your response, you MUST post it by calling mcp__github__update_issue_comment. Do NOT just respond with a normal response, the user will not see it." : ""} +- Never create new comments. Only update the existing comment using mcp__github_file_ops__update_claude_comment. +- This includes ALL responses: code reviews, answers to questions, progress updates, and final results.${eventData.isPR ? "\n- PR CRITICAL: After reading files and forming your response, you MUST post it by calling mcp__github_file_ops__update_claude_comment. Do NOT just respond with a normal response, the user will not see it." : ""} - You communicate exclusively by editing your single comment - not through any other means. - Use this spinner HTML when work is in progress: ${eventData.isPR && !eventData.claudeBranch ? `- Always push to the existing branch when triggered on a PR.` : `- IMPORTANT: You are already on the correct branch (${eventData.claudeBranch || "the created branch"}). Never create new branches when triggered on issues or closed/merged PRs.`} diff --git a/src/entrypoints/prepare.ts b/src/entrypoints/prepare.ts index c3e0b38..c69f08b 100644 --- a/src/entrypoints/prepare.ts +++ b/src/entrypoints/prepare.ts @@ -89,6 +89,7 @@ async function run() { context.repository.owner, context.repository.repo, branchInfo.currentBranch, + commentId.toString(), ); core.setOutput("mcp_config", mcpConfig); } catch (error) { diff --git a/src/mcp/github-file-ops-server.ts b/src/mcp/github-file-ops-server.ts index 19834c9..1a3e4ba 100644 --- a/src/mcp/github-file-ops-server.ts +++ b/src/mcp/github-file-ops-server.ts @@ -439,6 +439,122 @@ server.tool( }, ); +// Update Claude comment tool +server.tool( + "update_claude_comment", + "Update the Claude comment with progress and results (automatically handles both issue and PR comments)", + { + body: z.string().describe("The updated comment content"), + }, + async ({ body }) => { + try { + const githubToken = process.env.GITHUB_TOKEN; + const claudeCommentId = process.env.CLAUDE_COMMENT_ID; + const eventName = process.env.GITHUB_EVENT_NAME; + const isPR = process.env.IS_PR === "true"; + + if (!githubToken) { + throw new Error("GITHUB_TOKEN environment variable is required"); + } + if (!claudeCommentId) { + throw new Error("CLAUDE_COMMENT_ID environment variable is required"); + } + + const owner = REPO_OWNER; + const repo = REPO_NAME; + const commentId = parseInt(claudeCommentId, 10); + + // Determine if this is a PR review comment based on event type + const isPullRequestReviewComment = + eventName === "pull_request_review_comment"; + + let response; + let updateUrl: string; + + if (isPullRequestReviewComment) { + // Use the PR review comment API + updateUrl = `${GITHUB_API_URL}/repos/${owner}/${repo}/pulls/comments/${commentId}`; + } else { + // Use the issue comment API (works for both issues and PR general comments) + updateUrl = `${GITHUB_API_URL}/repos/${owner}/${repo}/issues/comments/${commentId}`; + } + + response = await fetch(updateUrl, { + method: "PATCH", + headers: { + Accept: "application/vnd.github+json", + Authorization: `Bearer ${githubToken}`, + "X-GitHub-Api-Version": "2022-11-28", + "Content-Type": "application/json", + }, + body: JSON.stringify({ body }), + }); + + if (!response.ok) { + const errorText = await response.text(); + + // If PR review comment update fails, fall back to issue comment API + if (isPullRequestReviewComment && response.status === 404) { + const fallbackUrl = `${GITHUB_API_URL}/repos/${owner}/${repo}/issues/comments/${commentId}`; + response = await fetch(fallbackUrl, { + method: "PATCH", + headers: { + Accept: "application/vnd.github+json", + Authorization: `Bearer ${githubToken}`, + "X-GitHub-Api-Version": "2022-11-28", + "Content-Type": "application/json", + }, + body: JSON.stringify({ body }), + }); + + if (!response.ok) { + const fallbackErrorText = await response.text(); + throw new Error( + `Failed to update comment: ${response.status} - ${fallbackErrorText}`, + ); + } + } else { + throw new Error( + `Failed to update comment: ${response.status} - ${errorText}`, + ); + } + } + + const updatedComment = await response.json(); + + return { + content: [ + { + type: "text", + text: JSON.stringify( + { + id: updatedComment.id, + html_url: updatedComment.html_url, + updated_at: updatedComment.updated_at, + }, + null, + 2, + ), + }, + ], + }; + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : String(error); + return { + content: [ + { + type: "text", + text: `Error: ${errorMessage}`, + }, + ], + error: errorMessage, + isError: true, + }; + } + }, +); + async function runServer() { const transport = new StdioServerTransport(); await server.connect(transport); diff --git a/src/mcp/install-mcp-server.ts b/src/mcp/install-mcp-server.ts index 462967d..9233f4b 100644 --- a/src/mcp/install-mcp-server.ts +++ b/src/mcp/install-mcp-server.ts @@ -5,6 +5,7 @@ export async function prepareMcpConfig( owner: string, repo: string, branch: string, + claudeCommentId?: string, ): Promise { try { const mcpConfig = { @@ -35,6 +36,9 @@ export async function prepareMcpConfig( REPO_NAME: repo, BRANCH_NAME: branch, REPO_DIR: process.env.GITHUB_WORKSPACE || process.cwd(), + ...(claudeCommentId && { CLAUDE_COMMENT_ID: claudeCommentId }), + GITHUB_EVENT_NAME: process.env.GITHUB_EVENT_NAME || "", + IS_PR: process.env.IS_PR || "false", }, }, }, diff --git a/test/create-prompt.test.ts b/test/create-prompt.test.ts index 94064b6..fa98ebd 100644 --- a/test/create-prompt.test.ts +++ b/test/create-prompt.test.ts @@ -636,7 +636,8 @@ describe("buildAllowedToolsString", () => { expect(result).toContain("LS"); expect(result).toContain("Read"); expect(result).toContain("Write"); - expect(result).toContain("mcp__github__update_issue_comment"); + expect(result).toContain("mcp__github_file_ops__update_claude_comment"); + expect(result).not.toContain("mcp__github__update_issue_comment"); expect(result).not.toContain("mcp__github__update_pull_request_comment"); expect(result).toContain("mcp__github_file_ops__commit_files"); expect(result).toContain("mcp__github_file_ops__delete_files"); @@ -660,8 +661,9 @@ describe("buildAllowedToolsString", () => { expect(result).toContain("LS"); expect(result).toContain("Read"); expect(result).toContain("Write"); + expect(result).toContain("mcp__github_file_ops__update_claude_comment"); expect(result).not.toContain("mcp__github__update_issue_comment"); - expect(result).toContain("mcp__github__update_pull_request_comment"); + expect(result).not.toContain("mcp__github__update_pull_request_comment"); expect(result).toContain("mcp__github_file_ops__commit_files"); expect(result).toContain("mcp__github_file_ops__delete_files"); });