@azure-devops/mcp 1.3.1-nightly.20250811 → 1.3.1-nightly.20250813

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.
package/README.md CHANGED
@@ -16,7 +16,7 @@ This TypeScript project provides a **local** MCP server for Azure DevOps, enabli
16
16
  3. [⚙️ Supported Tools](#️-supported-tools)
17
17
  4. [🔌 Installation & Getting Started](#-installation--getting-started)
18
18
  5. [📝 Troubleshooting](#-troubleshooting)
19
- 6. [🎩 Examples & Best Practices](#-samples--best-practices)
19
+ 6. [🎩 Examples & Best Practices](#-examples--best-practices)
20
20
  7. [🙋‍♀️ Frequently Asked Questions](#️-frequently-asked-questions)
21
21
  8. [📌 Contributing](#-contributing)
22
22
 
@@ -198,13 +198,13 @@ In your project, add a `.vscode\mcp.json` file with the following content:
198
198
 
199
199
  Save the file, then click 'Start'.
200
200
 
201
- <img src="./docs/media/start-mcp-server.gif" alt="start mcp server" width="250"/>
201
+ ![start mcp server](./docs/media/start-mcp-server.gif)
202
202
 
203
203
  In chat, switch to [Agent Mode](https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode).
204
204
 
205
205
  Click "Select Tools" and choose the available tools.
206
206
 
207
- <img src="./docs/media/configure-mcp-server-tools.gif" alt="configure mcp server tools" width="300"/>
207
+ ![configure mcp server tools](./docs/media/configure-mcp-server-tools.gif)
208
208
 
209
209
  Open GitHub Copilot Chat and try a prompt like `List ADO projects`.
210
210
 
@@ -79,7 +79,10 @@ function configureTestPlanTools(server, tokenProvider, connectionProvider) {
79
79
  server.tool(Test_Plan_Tools.create_test_case, "Creates a new test case work item.", {
80
80
  project: z.string().describe("The unique identifier (ID or name) of the Azure DevOps project."),
81
81
  title: z.string().describe("The title of the test case."),
82
- steps: z.string().optional().describe("The steps to reproduce the test case. Make sure to format each step as '1. Step one|Expected result one\n2. Step two|Expected result two"),
82
+ steps: z
83
+ .string()
84
+ .optional()
85
+ .describe("The steps to reproduce the test case. Make sure to format each step as '1. Step one|Expected result one\n2. Step two|Expected result two. USE '|' as the delimiter between step and expected result. DO NOT use '|' in the description of the step or expected result."),
83
86
  priority: z.number().optional().describe("The priority of the test case."),
84
87
  areaPath: z.string().optional().describe("The area path for the test case."),
85
88
  iterationPath: z.string().optional().describe("The iteration path for the test case."),
@@ -23,6 +23,7 @@ const WORKITEM_TOOLS = {
23
23
  update_work_items_batch: "wit_update_work_items_batch",
24
24
  work_items_link: "wit_work_items_link",
25
25
  work_item_unlink: "wit_work_item_unlink",
26
+ add_artifact_link: "wit_add_artifact_link",
26
27
  };
27
28
  function getLinkTypeFromName(name) {
28
29
  switch (name.toLowerCase()) {
@@ -104,15 +105,29 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
104
105
  // If no fields are provided, use the default set of fields
105
106
  const fieldsToUse = !fields || fields.length === 0 ? defaultFields : fields;
106
107
  const workitems = await workItemApi.getWorkItemsBatch({ ids, fields: fieldsToUse }, project);
107
- // Format the assignedTo field to include displayName and uniqueName
108
+ // List of identity fields that need to be transformed from objects to formatted strings
109
+ const identityFields = [
110
+ "System.AssignedTo",
111
+ "System.CreatedBy",
112
+ "System.ChangedBy",
113
+ "System.AuthorizedAs",
114
+ "Microsoft.VSTS.Common.ActivatedBy",
115
+ "Microsoft.VSTS.Common.ResolvedBy",
116
+ "Microsoft.VSTS.Common.ClosedBy",
117
+ ];
118
+ // Format identity fields to include displayName and uniqueName
108
119
  // Removing the identity object as the response. It's too much and not needed
109
120
  if (workitems && Array.isArray(workitems)) {
110
121
  workitems.forEach((item) => {
111
- if (item.fields && item.fields["System.AssignedTo"] && typeof item.fields["System.AssignedTo"] === "object") {
112
- const assignedTo = item.fields["System.AssignedTo"];
113
- const name = assignedTo.displayName || "";
114
- const email = assignedTo.uniqueName || "";
115
- item.fields["System.AssignedTo"] = `${name} <${email}>`.trim();
122
+ if (item.fields) {
123
+ identityFields.forEach((fieldName) => {
124
+ if (item.fields && item.fields[fieldName] && typeof item.fields[fieldName] === "object") {
125
+ const identityField = item.fields[fieldName];
126
+ const name = identityField.displayName || "";
127
+ const email = identityField.uniqueName || "";
128
+ item.fields[fieldName] = `${name} <${email}>`.trim();
129
+ }
130
+ });
116
131
  }
117
132
  });
118
133
  }
@@ -658,5 +673,137 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
658
673
  };
659
674
  }
660
675
  });
676
+ server.tool(WORKITEM_TOOLS.add_artifact_link, "Add artifact links (repository, branch, commit, builds) to work items. You can either provide the full vstfs URI or the individual components to build it automatically.", {
677
+ workItemId: z.number().describe("The ID of the work item to add the artifact link to."),
678
+ project: z.string().describe("The name or ID of the Azure DevOps project."),
679
+ // Option 1: Provide full URI directly
680
+ artifactUri: z.string().optional().describe("The complete VSTFS URI of the artifact to link. If provided, individual component parameters are ignored."),
681
+ // Option 2: Provide individual components to build URI automatically based on linkType
682
+ projectId: z.string().optional().describe("The project ID (GUID) containing the artifact. Required for Git artifacts when artifactUri is not provided."),
683
+ repositoryId: z.string().optional().describe("The repository ID (GUID) containing the artifact. Required for Git artifacts when artifactUri is not provided."),
684
+ branchName: z.string().optional().describe("The branch name (e.g., 'main'). Required when linkType is 'Branch'."),
685
+ commitId: z.string().optional().describe("The commit SHA hash. Required when linkType is 'Fixed in Commit'."),
686
+ pullRequestId: z.number().optional().describe("The pull request ID. Required when linkType is 'Pull Request'."),
687
+ buildId: z.number().optional().describe("The build ID. Required when linkType is 'Build', 'Found in build', or 'Integrated in build'."),
688
+ linkType: z
689
+ .enum([
690
+ "Branch",
691
+ "Build",
692
+ "Fixed in Changeset",
693
+ "Fixed in Commit",
694
+ "Found in build",
695
+ "Integrated in build",
696
+ "Model Link",
697
+ "Pull Request",
698
+ "Related Workitem",
699
+ "Result Attachment",
700
+ "Source Code File",
701
+ "Tag",
702
+ "Test Result",
703
+ "Wiki",
704
+ ])
705
+ .default("Branch")
706
+ .describe("Type of artifact link, defaults to 'Branch'. This determines both the link type and how to build the VSTFS URI from individual components."),
707
+ comment: z.string().optional().describe("Comment to include with the artifact link."),
708
+ }, async ({ workItemId, project, artifactUri, projectId, repositoryId, branchName, commitId, pullRequestId, buildId, linkType, comment }) => {
709
+ try {
710
+ const connection = await connectionProvider();
711
+ const workItemTrackingApi = await connection.getWorkItemTrackingApi();
712
+ let finalArtifactUri;
713
+ if (artifactUri) {
714
+ // Use the provided full URI
715
+ finalArtifactUri = artifactUri;
716
+ }
717
+ else {
718
+ // Build the URI from individual components based on linkType
719
+ switch (linkType) {
720
+ case "Branch":
721
+ if (!projectId || !repositoryId || !branchName) {
722
+ return {
723
+ content: [{ type: "text", text: "For 'Branch' links, 'projectId', 'repositoryId', and 'branchName' are required." }],
724
+ isError: true,
725
+ };
726
+ }
727
+ finalArtifactUri = `vstfs:///Git/Ref/${encodeURIComponent(projectId)}%2F${encodeURIComponent(repositoryId)}%2FGB${encodeURIComponent(branchName)}`;
728
+ break;
729
+ case "Fixed in Commit":
730
+ if (!projectId || !repositoryId || !commitId) {
731
+ return {
732
+ content: [{ type: "text", text: "For 'Fixed in Commit' links, 'projectId', 'repositoryId', and 'commitId' are required." }],
733
+ isError: true,
734
+ };
735
+ }
736
+ finalArtifactUri = `vstfs:///Git/Commit/${encodeURIComponent(projectId)}%2F${encodeURIComponent(repositoryId)}%2F${encodeURIComponent(commitId)}`;
737
+ break;
738
+ case "Pull Request":
739
+ if (!projectId || !repositoryId || pullRequestId === undefined) {
740
+ return {
741
+ content: [{ type: "text", text: "For 'Pull Request' links, 'projectId', 'repositoryId', and 'pullRequestId' are required." }],
742
+ isError: true,
743
+ };
744
+ }
745
+ finalArtifactUri = `vstfs:///Git/PullRequestId/${encodeURIComponent(projectId)}%2F${encodeURIComponent(repositoryId)}%2F${encodeURIComponent(pullRequestId.toString())}`;
746
+ break;
747
+ case "Build":
748
+ case "Found in build":
749
+ case "Integrated in build":
750
+ if (buildId === undefined) {
751
+ return {
752
+ content: [{ type: "text", text: `For '${linkType}' links, 'buildId' is required.` }],
753
+ isError: true,
754
+ };
755
+ }
756
+ finalArtifactUri = `vstfs:///Build/Build/${encodeURIComponent(buildId.toString())}`;
757
+ break;
758
+ default:
759
+ return {
760
+ content: [{ type: "text", text: `URI building from components is not supported for link type '${linkType}'. Please provide the full 'artifactUri' instead.` }],
761
+ isError: true,
762
+ };
763
+ }
764
+ }
765
+ // Create the patch document for adding an artifact link relation
766
+ const patchDocument = [
767
+ {
768
+ op: "add",
769
+ path: "/relations/-",
770
+ value: {
771
+ rel: "ArtifactLink",
772
+ url: finalArtifactUri,
773
+ attributes: {
774
+ name: linkType,
775
+ ...(comment && { comment }),
776
+ },
777
+ },
778
+ },
779
+ ];
780
+ // Use the WorkItem API to update the work item with the new relation
781
+ const workItem = await workItemTrackingApi.updateWorkItem({}, patchDocument, workItemId, project);
782
+ if (!workItem) {
783
+ return { content: [{ type: "text", text: "Work item update failed" }], isError: true };
784
+ }
785
+ return {
786
+ content: [
787
+ {
788
+ type: "text",
789
+ text: JSON.stringify({
790
+ workItemId,
791
+ artifactUri: finalArtifactUri,
792
+ linkType,
793
+ comment: comment || null,
794
+ success: true,
795
+ }, null, 2),
796
+ },
797
+ ],
798
+ };
799
+ }
800
+ catch (error) {
801
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
802
+ return {
803
+ content: [{ type: "text", text: `Error adding artifact link to work item: ${errorMessage}` }],
804
+ isError: true,
805
+ };
806
+ }
807
+ });
661
808
  }
662
809
  export { WORKITEM_TOOLS, configureWorkItemTools };
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const packageVersion = "1.3.1-nightly.20250811";
1
+ export const packageVersion = "1.3.1-nightly.20250813";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@azure-devops/mcp",
3
- "version": "1.3.1-nightly.20250811",
3
+ "version": "1.3.1-nightly.20250813",
4
4
  "description": "MCP server for interacting with Azure DevOps",
5
5
  "license": "MIT",
6
6
  "author": "Microsoft Corporation",