@azure-devops/mcp 2.9.0-nightly.20260807 → 2.9.0-nightly.20260809

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.
@@ -45,10 +45,7 @@ async function searchIdentities(identity, tokenProvider, connectionProvider, use
45
45
  }
46
46
  return await response.json();
47
47
  }
48
- /**
49
- * Gets the user ID from email or unique name using Azure DevOps Identity API
50
- */
51
- async function getUserIdFromEmail(userEmail, tokenProvider, connectionProvider, userAgentProvider) {
48
+ async function getUserIdentityFromEmail(userEmail, tokenProvider, connectionProvider, userAgentProvider) {
52
49
  const identities = await searchIdentities(userEmail, tokenProvider, connectionProvider, userAgentProvider);
53
50
  if (!identities || identities.value?.length === 0) {
54
51
  throw new Error(`No user found with email/unique name: ${userEmail}`);
@@ -57,6 +54,13 @@ async function getUserIdFromEmail(userEmail, tokenProvider, connectionProvider,
57
54
  if (!firstIdentity.id) {
58
55
  throw new Error(`No ID found for user with email/unique name: ${userEmail}`);
59
56
  }
60
- return firstIdentity.id;
57
+ return { id: firstIdentity.id, displayName: firstIdentity.providerDisplayName ?? userEmail };
58
+ }
59
+ /**
60
+ * Gets the user ID from email or unique name using Azure DevOps Identity API
61
+ */
62
+ async function getUserIdFromEmail(userEmail, tokenProvider, connectionProvider, userAgentProvider) {
63
+ const identity = await getUserIdentityFromEmail(userEmail, tokenProvider, connectionProvider, userAgentProvider);
64
+ return identity.id;
61
65
  }
62
- export { getCurrentUserDetails, getUserIdFromEmail, searchIdentities };
66
+ export { getCurrentUserDetails, getUserIdFromEmail, getUserIdentityFromEmail, searchIdentities };
@@ -8,6 +8,7 @@ import { z } from "zod";
8
8
  import { batchApiVersion, markdownCommentsApiVersion, getEnumKeys, safeEnumConvert, encodeFormattedValue } from "../utils.js";
9
9
  import { elicitProject, elicitTeam } from "../shared/elicitations.js";
10
10
  import { createExternalContentResponse } from "../shared/content-safety.js";
11
+ import { getUserIdentityFromEmail } from "./auth.js";
11
12
  const WORKITEM_TOOLS = {
12
13
  wit_work_item: "wit_work_item",
13
14
  wit_query: "wit_query",
@@ -57,6 +58,36 @@ function getArtifactLinkAttributeName(linkType) {
57
58
  return linkType;
58
59
  }
59
60
  }
61
+ function escapeHtml(value) {
62
+ const entities = {
63
+ "&": "&",
64
+ "<": "&lt;",
65
+ ">": "&gt;",
66
+ '"': "&quot;",
67
+ "'": "&#39;",
68
+ };
69
+ return value.replace(/[&<>"']/g, (character) => entities[character]);
70
+ }
71
+ async function resolveCommentMentions(text, format, tokenProvider, connectionProvider, userAgentProvider) {
72
+ const emailMatches = [...text.matchAll(/@<([^<>\s]+@[^<>\s]+)>/g)];
73
+ if (emailMatches.length === 0)
74
+ return text;
75
+ const identities = new Map();
76
+ for (const email of new Set(emailMatches.map((match) => match[1]))) {
77
+ try {
78
+ identities.set(email, await getUserIdentityFromEmail(email, tokenProvider, connectionProvider, userAgentProvider));
79
+ }
80
+ catch {
81
+ // Leave mentions unchanged when their identities cannot be resolved.
82
+ }
83
+ }
84
+ return text.replace(/@<([^<>\s]+@[^<>\s]+)>/g, (mention, email) => {
85
+ const identity = identities.get(email);
86
+ if (!identity)
87
+ return escapeHtml(mention);
88
+ return format === "Markdown" || format === undefined ? `@<${identity.id}>` : `<a href="#" data-vss-mention="version:2.0,${identity.id}">@${escapeHtml(identity.displayName)}</a>`;
89
+ });
90
+ }
60
91
  function configureWorkItemTools(server, tokenProvider, connectionProvider, userAgentProvider) {
61
92
  // --- wit_work_item ----------------------------------------------------------
62
93
  server.tool(WORKITEM_TOOLS.wit_work_item, "Retrieve work item data for a project. Use the action parameter to specify the operation.", {
@@ -678,6 +709,7 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
678
709
  const orgUrl = connection.serverUrl;
679
710
  const accessToken = await tokenProvider();
680
711
  const formatParameter = (format ?? "Markdown") === "Markdown" ? 0 : 1;
712
+ const resolvedText = await resolveCommentMentions(text, format, tokenProvider, connectionProvider, userAgentProvider);
681
713
  if (action === "add") {
682
714
  const response = await fetch(`${orgUrl}/${encodeURIComponent(resolvedProject)}/_apis/wit/workItems/${workItemId}/comments?format=${formatParameter}&api-version=${markdownCommentsApiVersion}`, {
683
715
  method: "POST",
@@ -686,7 +718,7 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
686
718
  "Content-Type": "application/json",
687
719
  "User-Agent": userAgentProvider(),
688
720
  },
689
- body: JSON.stringify({ text }),
721
+ body: JSON.stringify({ text: resolvedText }),
690
722
  });
691
723
  if (!response.ok) {
692
724
  throw new Error(`Failed to add a work item comment: ${response.statusText}`);
@@ -703,7 +735,7 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
703
735
  "Content-Type": "application/json",
704
736
  "User-Agent": userAgentProvider(),
705
737
  },
706
- body: JSON.stringify({ text }),
738
+ body: JSON.stringify({ text: resolvedText }),
707
739
  });
708
740
  if (!response.ok) {
709
741
  throw new Error(`Failed to update work item comment: ${response.statusText}`);
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const packageVersion = "2.9.0-nightly.20260807";
1
+ export const packageVersion = "2.9.0-nightly.20260809";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@azure-devops/mcp",
3
- "version": "2.9.0-nightly.20260807",
3
+ "version": "2.9.0-nightly.20260809",
4
4
  "mcpName": "microsoft.com/azure-devops",
5
5
  "description": "MCP server for interacting with Azure DevOps",
6
6
  "license": "MIT",