@azure-devops/mcp 2.10.0-nightly.20260923 → 2.10.0-nightly.20260924

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -32,6 +32,7 @@ const TOOL_ANNOTATIONS = {
32
32
  repo_branch: READ_ONLY_ANNOTATIONS,
33
33
  repo_create_branch: ADDITIVE_ANNOTATIONS,
34
34
  repo_file: READ_ONLY_ANNOTATIONS,
35
+ repo_file_write: MUTATING_ANNOTATIONS,
35
36
  repo_pull_request: READ_ONLY_ANNOTATIONS,
36
37
  repo_pull_request_org: READ_ONLY_ANNOTATIONS,
37
38
  repo_pull_request_thread: READ_ONLY_ANNOTATIONS,
@@ -1,6 +1,6 @@
1
1
  // Copyright (c) Microsoft Corporation.
2
2
  // Licensed under the MIT License.
3
- import { PullRequestStatus, GitVersionType, GitPullRequestQueryType, CommentThreadStatus, GitPullRequestMergeStrategy, VersionControlRecursionType, } from "azure-devops-node-api/interfaces/GitInterfaces.js";
3
+ import { PullRequestStatus, GitVersionType, GitPullRequestQueryType, CommentThreadStatus, GitPullRequestMergeStrategy, ItemContentType, VersionControlChangeType, VersionControlRecursionType, } from "azure-devops-node-api/interfaces/GitInterfaces.js";
4
4
  import { z } from "zod";
5
5
  import { getCurrentUserDetails, getUserIdFromEmail } from "./auth.js";
6
6
  import { extractAdoStreamError, getEnumKeys, streamToString, apiVersion } from "../utils.js";
@@ -13,6 +13,7 @@ const REPO_TOOLS = {
13
13
  repo_pull_request_thread: "repo_pull_request_thread",
14
14
  repo_branch: "repo_branch",
15
15
  repo_file: "repo_file",
16
+ repo_file_write: "repo_file_write",
16
17
  repo_search_commits: "repo_search_commits",
17
18
  repo_pull_request_write: "repo_pull_request_write",
18
19
  repo_pull_request_thread_write: "repo_pull_request_thread_write",
@@ -199,9 +200,13 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
199
200
  project: z.string().optional().describe("Project ID or project name. Required for list_by_commits. Optional for get and list."),
200
201
  includeWorkItemRefs: z.boolean().optional().default(false).describe("Whether to include work item references. Used for get."),
201
202
  includeLabels: z.boolean().optional().default(false).describe("Whether to include labels. Used for get."),
202
- includeChangedFiles: z.boolean().optional().default(false).describe("Whether to include the list of changed files. Used for get."),
203
- top: z.coerce.number().default(100).describe("The maximum number of pull requests to return. Used for list. Defaults to 100."),
204
- skip: z.coerce.number().default(0).describe("The number of pull requests to skip. Used for list. Defaults to 0."),
203
+ includeChangedFiles: z
204
+ .boolean()
205
+ .optional()
206
+ .default(false)
207
+ .describe("Whether to include the list of changed files. When changedFilesSummary returns a nextSkip greater than 0, pass nextSkip and nextTop back as skip and top for the next page. Used for get."),
208
+ top: z.coerce.number().default(100).describe("The maximum number of pull requests to return (list), or of changed files to return (get with includeChangedFiles). Defaults to 100."),
209
+ skip: z.coerce.number().default(0).describe("The number of pull requests to skip (list), or of changed files to skip (get with includeChangedFiles). Defaults to 0."),
205
210
  created_by_me: z.boolean().default(false).describe("Filter pull requests created by the current user. Used for list."),
206
211
  created_by_user: z.string().optional().describe("Filter pull requests created by a specific user email. Used for list."),
207
212
  i_am_reviewer: z.boolean().default(false).describe("Filter pull requests where the current user is a reviewer. Used for list."),
@@ -249,7 +254,7 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
249
254
  if (iterations?.length) {
250
255
  const latestIteration = iterations[iterations.length - 1];
251
256
  if (latestIteration.id != null) {
252
- const changes = await gitApi.getPullRequestIterationChanges(repositoryId, pullRequestId, latestIteration.id, project);
257
+ const changes = await gitApi.getPullRequestIterationChanges(repositoryId, pullRequestId, latestIteration.id, project, top, skip);
253
258
  enhancedResponse = {
254
259
  ...enhancedResponse,
255
260
  changedFilesSummary: {
@@ -567,6 +572,63 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
567
572
  return { content: [{ type: "text", text: `Error with file operation: ${errorMessage}` }], isError: true };
568
573
  }
569
574
  });
575
+ // --- repo_file_write -------------------------------------------------------
576
+ server.tool(REPO_TOOLS.repo_file_write, "Create or update a text file in an existing repository branch and commit the change.", {
577
+ action: z.enum(["create", "update"]).describe("Whether to create a new file or update an existing file."),
578
+ repositoryId: z.string().describe("The ID or name of the repository."),
579
+ project: z.string().optional().describe("Project ID or project name. Required when repositoryId is a name."),
580
+ branchName: z.string().min(1).describe("The name of the existing branch to update, without the 'refs/heads/' prefix."),
581
+ path: z.string().min(1).describe("The absolute repository path of the file, for example '/src/example.ts'."),
582
+ content: z.string().describe("The complete UTF-8 text content to write to the file."),
583
+ commitMessage: z.string().min(1).describe("The commit message for the file change."),
584
+ expectedOldObjectId: z
585
+ .string()
586
+ .optional()
587
+ .describe("The expected current commit ID of the branch. When provided, the push fails if the branch has moved. If omitted, the current branch head is retrieved before pushing."),
588
+ }, async ({ action, repositoryId, project, branchName, path, content, commitMessage, expectedOldObjectId }) => {
589
+ try {
590
+ const connection = await connectionProvider();
591
+ const gitApi = await connection.getGitApi();
592
+ const refName = `refs/heads/${branchName}`;
593
+ const refs = await gitApi.getRefs(repositoryId, project, "heads/", false, false, undefined, false, undefined, branchName);
594
+ const branch = refs.find((ref) => ref.name === refName);
595
+ if (!branch?.objectId) {
596
+ return { content: [{ type: "text", text: `Error: Branch '${branchName}' not found in repository ${repositoryId}` }], isError: true };
597
+ }
598
+ if (expectedOldObjectId && expectedOldObjectId !== branch.objectId) {
599
+ return { content: [{ type: "text", text: `Error: Branch '${branchName}' has moved from expected commit ${expectedOldObjectId}` }], isError: true };
600
+ }
601
+ const oldObjectId = branch.objectId;
602
+ const filePath = path.startsWith("/") ? path : `/${path}`;
603
+ const push = await gitApi.createPush({
604
+ refUpdates: [{ name: refName, oldObjectId }],
605
+ commits: [
606
+ {
607
+ comment: commitMessage,
608
+ changes: [
609
+ {
610
+ changeType: action === "create" ? VersionControlChangeType.Add : VersionControlChangeType.Edit,
611
+ item: { path: filePath },
612
+ newContent: { content, contentType: ItemContentType.RawText },
613
+ },
614
+ ],
615
+ },
616
+ ],
617
+ }, repositoryId, project);
618
+ return {
619
+ content: [
620
+ {
621
+ type: "text",
622
+ text: JSON.stringify({ pushId: push.pushId, commitId: push.commits?.[0]?.commitId, branchName, path: filePath }, null, 2),
623
+ },
624
+ ],
625
+ };
626
+ }
627
+ catch (error) {
628
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
629
+ return { content: [{ type: "text", text: `Error writing repository file: ${errorMessage}` }], isError: true };
630
+ }
631
+ });
570
632
  // --- repo_search_commits ---------------------------------------------------
571
633
  server.tool(REPO_TOOLS.repo_search_commits, "Search commits with filtering by text, author, date range, and more.", {
572
634
  searchText: z.string().describe("Keywords to search for in commit messages"),
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const packageVersion = "2.10.0-nightly.20260923";
1
+ export const packageVersion = "2.10.0-nightly.20260924";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@azure-devops/mcp",
3
- "version": "2.10.0-nightly.20260923",
3
+ "version": "2.10.0-nightly.20260924",
4
4
  "mcpName": "microsoft.com/azure-devops",
5
5
  "description": "MCP server for interacting with Azure DevOps",
6
6
  "license": "MIT",