mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-23 06:54:13 +08:00
feat: add unified update_claude_comment tool
- Add new update_claude_comment tool that automatically handles both issue and PR comments - Remove individual update_issue_comment and update_pull_request_comment tools - Pass CLAUDE_COMMENT_ID, GITHUB_EVENT_NAME, and IS_PR to MCP server environment - Simplify Claude's comment update workflow by removing need for owner/repo/commentId params - Update prompts and tests to use the new unified tool
This commit is contained in:
@@ -31,6 +31,7 @@ const BASE_ALLOWED_TOOLS = [
|
|||||||
"Write",
|
"Write",
|
||||||
"mcp__github_file_ops__commit_files",
|
"mcp__github_file_ops__commit_files",
|
||||||
"mcp__github_file_ops__delete_files",
|
"mcp__github_file_ops__delete_files",
|
||||||
|
"mcp__github_file_ops__update_claude_comment",
|
||||||
];
|
];
|
||||||
const DISALLOWED_TOOLS = ["WebSearch", "WebFetch"];
|
const DISALLOWED_TOOLS = ["WebSearch", "WebFetch"];
|
||||||
|
|
||||||
@@ -40,15 +41,6 @@ export function buildAllowedToolsString(
|
|||||||
): string {
|
): string {
|
||||||
let baseTools = [...BASE_ALLOWED_TOOLS];
|
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(",");
|
let allAllowedTools = baseTools.join(",");
|
||||||
if (customAllowedTools) {
|
if (customAllowedTools) {
|
||||||
allAllowedTools = `${allAllowedTools},${customAllowedTools}`;
|
allAllowedTools = `${allAllowedTools},${customAllowedTools}`;
|
||||||
@@ -447,33 +439,15 @@ ${sanitizeContent(context.directPrompt)}
|
|||||||
</direct_prompt>`
|
</direct_prompt>`
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
${
|
${`<comment_tool_info>
|
||||||
eventData.eventName === "pull_request_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.
|
||||||
? `<comment_tool_info>
|
|
||||||
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.
|
|
||||||
|
|
||||||
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"
|
"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.
|
||||||
</comment_tool_info>`
|
</comment_tool_info>`}
|
||||||
: `<comment_tool_info>
|
|
||||||
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.
|
|
||||||
</comment_tool_info>`
|
|
||||||
}
|
|
||||||
|
|
||||||
Your task is to analyze the context, understand the request, and provide helpful responses and/or implement code changes as needed.
|
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:
|
1. Create a Todo List:
|
||||||
- Use your GitHub comment to maintain a detailed task list based on the request.
|
- Use your GitHub comment to maintain a detailed task list based on the request.
|
||||||
- Format todos as a checklist (- [ ] for incomplete, - [x] for complete).
|
- 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:
|
2. Gather Context:
|
||||||
- Analyze the pre-fetched data provided above.
|
- 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
|
- Look for bugs, security issues, performance problems, and other issues
|
||||||
- Suggest improvements for readability and maintainability
|
- Suggest improvements for readability and maintainability
|
||||||
- Check for best practices and coding standards
|
- 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.
|
- Formulate a concise, technical, and helpful response based on the context.
|
||||||
- Reference specific code with inline formatting or code blocks.
|
- Reference specific code with inline formatting or code blocks.
|
||||||
- Include relevant file paths and line numbers when applicable.
|
- 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:
|
B. For Straightforward Changes:
|
||||||
- Use file system tools to make the change locally.
|
- Use file system tools to make the change locally.
|
||||||
@@ -576,8 +550,8 @@ ${context.directPrompt ? ` - DIRECT INSTRUCTION: A direct instruction was prov
|
|||||||
|
|
||||||
Important Notes:
|
Important Notes:
|
||||||
- All communication must happen through GitHub PR comments.
|
- 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}.
|
- 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__update_issue_comment. Do NOT just respond with a normal response, the user will not see it." : ""}
|
- 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.
|
- You communicate exclusively by editing your single comment - not through any other means.
|
||||||
- Use this spinner HTML when work is in progress: <img src="https://github.com/user-attachments/assets/5ac382c7-e004-429b-8e35-7feb3e8f9c6f" width="14px" height="14px" style="vertical-align: middle; margin-left: 4px;" />
|
- Use this spinner HTML when work is in progress: <img src="https://github.com/user-attachments/assets/5ac382c7-e004-429b-8e35-7feb3e8f9c6f" width="14px" height="14px" style="vertical-align: middle; margin-left: 4px;" />
|
||||||
${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.`}
|
${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.`}
|
||||||
|
|||||||
@@ -85,13 +85,14 @@ async function run() {
|
|||||||
|
|
||||||
// Step 11: Get MCP configuration
|
// Step 11: Get MCP configuration
|
||||||
const additionalMcpConfig = process.env.MCP_CONFIG || "";
|
const additionalMcpConfig = process.env.MCP_CONFIG || "";
|
||||||
const mcpConfig = await prepareMcpConfig(
|
const mcpConfig = await prepareMcpConfig({
|
||||||
githubToken,
|
githubToken,
|
||||||
context.repository.owner,
|
owner: context.repository.owner,
|
||||||
context.repository.repo,
|
repo: context.repository.repo,
|
||||||
branchInfo.currentBranch,
|
branch: branchInfo.currentBranch,
|
||||||
additionalMcpConfig,
|
additionalMcpConfig,
|
||||||
);
|
claudeCommentId: commentId.toString(),
|
||||||
|
});
|
||||||
core.setOutput("mcp_config", mcpConfig);
|
core.setOutput("mcp_config", mcpConfig);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
|
|||||||
@@ -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() {
|
async function runServer() {
|
||||||
const transport = new StdioServerTransport();
|
const transport = new StdioServerTransport();
|
||||||
await server.connect(transport);
|
await server.connect(transport);
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
import * as core from "@actions/core";
|
import * as core from "@actions/core";
|
||||||
|
|
||||||
export async function prepareMcpConfig(
|
type PrepareConfigParams = {
|
||||||
githubToken: string,
|
githubToken: string;
|
||||||
owner: string,
|
owner: string;
|
||||||
repo: string,
|
repo: string;
|
||||||
branch: string,
|
branch: string;
|
||||||
additionalMcpConfig?: string,
|
additionalMcpConfig?: string;
|
||||||
): Promise<string> {
|
claudeCommentId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function prepareMcpConfig(params: PrepareConfigParams): Promise<string> {
|
||||||
|
const { githubToken, owner, repo, branch, additionalMcpConfig, claudeCommentId } = params;
|
||||||
try {
|
try {
|
||||||
const baseMcpConfig = {
|
const baseMcpConfig = {
|
||||||
mcpServers: {
|
mcpServers: {
|
||||||
@@ -36,6 +40,9 @@ export async function prepareMcpConfig(
|
|||||||
REPO_NAME: repo,
|
REPO_NAME: repo,
|
||||||
BRANCH_NAME: branch,
|
BRANCH_NAME: branch,
|
||||||
REPO_DIR: process.env.GITHUB_WORKSPACE || process.cwd(),
|
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",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -636,7 +636,8 @@ describe("buildAllowedToolsString", () => {
|
|||||||
expect(result).toContain("LS");
|
expect(result).toContain("LS");
|
||||||
expect(result).toContain("Read");
|
expect(result).toContain("Read");
|
||||||
expect(result).toContain("Write");
|
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).not.toContain("mcp__github__update_pull_request_comment");
|
||||||
expect(result).toContain("mcp__github_file_ops__commit_files");
|
expect(result).toContain("mcp__github_file_ops__commit_files");
|
||||||
expect(result).toContain("mcp__github_file_ops__delete_files");
|
expect(result).toContain("mcp__github_file_ops__delete_files");
|
||||||
@@ -660,8 +661,9 @@ describe("buildAllowedToolsString", () => {
|
|||||||
expect(result).toContain("LS");
|
expect(result).toContain("LS");
|
||||||
expect(result).toContain("Read");
|
expect(result).toContain("Read");
|
||||||
expect(result).toContain("Write");
|
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).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__commit_files");
|
||||||
expect(result).toContain("mcp__github_file_ops__delete_files");
|
expect(result).toContain("mcp__github_file_ops__delete_files");
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user