Skip to main content
Glama

create_pull_request_comment

Add comments to pull requests, including file-specific or line-specific feedback, and manage thread statuses for effective collaboration in Azure DevOps.

Instructions

Add a comment to a pull request

Input Schema

NameRequiredDescriptionDefault
contentYesComment content
filePathNoFile path for file-specific comments (optional)
lineNumberNoLine number for line-specific comments (optional)
pullRequestIdYesID of the pull request
statusNoThread status (optional)
threadIdNoThread ID for replies (optional)

Input Schema (JSON Schema)

{ "properties": { "content": { "description": "Comment content", "type": "string" }, "filePath": { "description": "File path for file-specific comments (optional)", "type": "string" }, "lineNumber": { "description": "Line number for line-specific comments (optional)", "type": "number" }, "pullRequestId": { "description": "ID of the pull request", "type": "number" }, "status": { "description": "Thread status (optional)", "enum": [ "active", "fixed", "pending", "wontfix", "closed" ], "type": "string" }, "threadId": { "description": "Thread ID for replies (optional)", "type": "number" } }, "required": [ "pullRequestId", "content" ], "type": "object" }

Implementation Reference

  • Main handler function executing the tool: parses input with Zod schema, uses Azure DevOps Git client to create comments or threads, supports file/line-specific positioning by finding iterations and changes.
    export async function createPullRequestComment(rawParams: any) { // Parse arguments with defaults from environment variables const params = createPullRequestCommentSchema.parse({ pullRequestId: rawParams.pullRequestId, content: rawParams.content, threadId: rawParams.threadId, filePath: rawParams.filePath, lineNumber: rawParams.lineNumber, status: rawParams.status, }); console.error("[API] Creating PR comment:", params); try { // Get the Git API client const gitClient = await getGitClient(); if (params.threadId) { // Reply to an existing thread console.error(`[API] Adding comment to thread ${params.threadId}`); const comment = { content: params.content, parentCommentId: 0, // Root-level comment in thread }; const commentUrl = `${ORG_URL}/${DEFAULT_PROJECT}/_apis/git/repositories/${DEFAULT_REPOSITORY}/pullRequests/${params.pullRequestId}/threads/${params.threadId}/comments?api-version=7.0`; const result = await makeAzureDevOpsRequest(commentUrl, "POST", comment); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; } else { // Create a new thread let thread: any = { comments: [ { content: params.content, commentType: 1, // 1 = text comment }, ], status: params.status || "active", }; // Handle file and line-specific comments if (params.filePath) { console.error( `[API] Creating code comment on file: ${params.filePath}` ); // Get iterations in ascending order const iterationsUrl = `${ORG_URL}/${DEFAULT_PROJECT}/_apis/git/repositories/${DEFAULT_REPOSITORY}/pullRequests/${params.pullRequestId}/iterations?api-version=7.1-preview.1`; const iterationsResponse = await makeAzureDevOpsRequest(iterationsUrl); if ( !iterationsResponse.value || iterationsResponse.value.length === 0 ) { throw new Error("No iterations found for pull request"); } // Normalize file path const normalizedPath = params.filePath.replace(/^\/+/, ""); console.error(`[API] Looking for file: ${normalizedPath}`); // Find the iteration where the file existed let targetIteration = null; let targetFileChange = null; for (const iteration of iterationsResponse.value) { console.error(`[API] Checking iteration ${iteration.id}`); const changesUrl = `${ORG_URL}/${DEFAULT_PROJECT}/_apis/git/repositories/${DEFAULT_REPOSITORY}/pullRequests/${params.pullRequestId}/iterations/${iteration.id}/changes?api-version=7.1-preview.1`; const changes = await makeAzureDevOpsRequest(changesUrl); console.error( `[API] Iteration ${iteration.id} changes:`, JSON.stringify( changes.changeEntries?.map((c: any) => c.item?.path), null, 2 ) ); // Try different path formats const pathVariations = [ normalizedPath, normalizedPath.replace(/^\/+/, ""), `/${normalizedPath}`, normalizedPath.toLowerCase(), normalizedPath.replace(/^\/+/, "").toLowerCase(), ]; console.error(`[API] Trying path variations:`, pathVariations); const fileChange = changes.changeEntries?.find( (change: { item?: { path?: string } }) => { const changePath = change.item?.path || ""; const match = pathVariations.some( (p) => changePath.toLowerCase() === p.toLowerCase() ); if (match) { console.error( `[API] Found match: ${changePath} matches ${pathVariations.find( (p) => changePath.toLowerCase() === p.toLowerCase() )}` ); } return match; } ); if (fileChange) { targetIteration = iteration; targetFileChange = fileChange; console.error( `[API] Found file in iteration ${iteration.id} with path ${fileChange.item.path}` ); break; } } if (!targetIteration || !targetFileChange) { throw new Error(`File ${normalizedPath} not found in any iteration`); } // Create thread with file position thread.threadContext = { filePath: normalizedPath, rightFileStart: { line: params.lineNumber, offset: 1, }, rightFileEnd: { line: params.lineNumber, offset: 1, }, }; // Set up the thread with version information const targetIterationId = Number(targetIteration.id); console.error( `[API] Using iteration ${targetIterationId} with change ID ${targetFileChange.changeTrackingId}` ); // Set thread properties for version control thread.properties = { "Microsoft.TeamFoundation.Discussion.SourceCommitId": { $type: "System.String", $value: targetFileChange.item.commitId, }, "Microsoft.TeamFoundation.Discussion.TargetCommitId": { $type: "System.String", $value: targetFileChange.item.commitId, }, "Microsoft.TeamFoundation.Discussion.Iteration": { $type: "System.String", $value: targetIterationId.toString(), }, }; // Set iteration context thread.pullRequestThreadContext = { iterationContext: { firstComparingIteration: targetIterationId, secondComparingIteration: targetIterationId, }, changeTrackingId: targetFileChange.changeTrackingId, }; thread.comments = [ { parentCommentId: 0, content: params.content, commentType: 1, }, ]; thread.status = "active"; console.error( "[API] Thread context:", JSON.stringify( { iteration: targetIteration.id, changeTracking: targetFileChange.changeTrackingId, }, null, 2 ) ); } else { console.error("[API] Creating general PR comment"); } // Create the new thread const threadUrl = `${ORG_URL}/${DEFAULT_PROJECT}/_apis/git/repositories/${DEFAULT_REPOSITORY}/pullRequests/${params.pullRequestId}/threads?api-version=7.0`; const result = await makeAzureDevOpsRequest(threadUrl, "POST", thread); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; } } catch (error) { logError("Error creating PR comment", error); throw error; } }
  • Zod schema for validating input parameters to the createPullRequestComment handler.
    export const createPullRequestCommentSchema = z.object({ pullRequestId: z.number(), content: z.string(), threadId: z.number().optional(), // For replying to existing threads filePath: z.string().optional(), // For file-specific comments lineNumber: z.number().optional(), // For line-specific comments status: z .enum(["active", "fixed", "pending", "wontfix", "closed"]) .optional(), // Thread status }); export type CreatePullRequestCommentParams = z.infer< typeof createPullRequestCommentSchema >;
  • Tool registration definition within pullRequestTools array, including name, description, and inputSchema; exported and included in server's tool list.
    { name: "create_pull_request_comment", description: "Add a comment to a pull request", inputSchema: { type: "object", properties: { pullRequestId: { type: "number", description: "ID of the pull request", }, content: { type: "string", description: "Comment content", }, threadId: { type: "number", description: "Thread ID for replies (optional)", }, filePath: { type: "string", description: "File path for file-specific comments (optional)", }, lineNumber: { type: "number", description: "Line number for line-specific comments (optional)", }, status: { type: "string", enum: ["active", "fixed", "pending", "wontfix", "closed"], description: "Thread status (optional)", }, }, required: ["pullRequestId", "content"], }, },
  • src/index.ts:83-84 (registration)
    Dispatcher switch case in main MCP server handler that routes calls to the createPullRequestComment function.
    case "create_pull_request_comment": return await createPullRequestComment(request.params.arguments || {});
  • src/index.ts:24-31 (registration)
    Import of the handler function and tools array from pullRequests module into main index.
    listPullRequests, getPullRequest, createPullRequest, createPullRequestComment, getPullRequestDiff, updatePullRequest, // Added import pullRequestTools, } from "./tools/pullRequests.js";

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mmruesch12/azdo-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server