@azure-devops/mcp 2.8.0-nightly.20260707 → 2.8.0-nightly.20260710

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.
@@ -474,6 +474,22 @@ function configureTestPlanTools(server, tokenProvider, connectionProvider, userA
474
474
  }
475
475
  });
476
476
  }
477
+ /*
478
+ * Format step content by converting Markdown markers to HTML and wrapping in the ADO rich text
479
+ * envelope. The entire HTML string is then XML-escaped for storage in the parameterizedString
480
+ * element, which is the format Azure DevOps expects for rendered step content.
481
+ */
482
+ function formatStepContent(text) {
483
+ // Convert Markdown markers to HTML tags (** before * and __ before _ to avoid conflicts)
484
+ const htmlContent = text
485
+ .replace(/\*\*(.+?)\*\*/g, "<b>$1</b>")
486
+ .replace(/\*(.+?)\*/g, "<i>$1</i>")
487
+ .replace(/__(.+?)__/g, "<u>$1</u>")
488
+ .replace(/`(.+?)`/g, "<code>$1</code>")
489
+ .replace(/\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g, '<a href="$2">$1</a>');
490
+ // Wrap in ADO rich text envelope and XML-escape the entire HTML string
491
+ return escapeXml(`${htmlContent}`);
492
+ }
477
493
  /*
478
494
  * Helper function to convert steps text to XML format required
479
495
  */
@@ -491,8 +507,8 @@ function convertStepsToXml(steps) {
491
507
  const expectedText = expectedPart || "Verify step completes successfully";
492
508
  xmlSteps += `
493
509
  <step id="${i + 1}" type="ActionStep">
494
- <parameterizedString isformatted="true">${escapeXml(stepText)}</parameterizedString>
495
- <parameterizedString isformatted="true">${escapeXml(expectedText)}</parameterizedString>
510
+ <parameterizedString isformatted="true">${formatStepContent(stepText)}</parameterizedString>
511
+ <parameterizedString isformatted="true">${formatStepContent(expectedText)}</parameterizedString>
496
512
  </step>`;
497
513
  }
498
514
  }
@@ -63,6 +63,14 @@ function getLinkTypeFromName(name) {
63
63
  throw new Error(`Unknown link type: ${name}`);
64
64
  }
65
65
  }
66
+ function getArtifactLinkAttributeName(linkType) {
67
+ switch (linkType) {
68
+ case "Wiki":
69
+ return "Wiki Page";
70
+ default:
71
+ return linkType;
72
+ }
73
+ }
66
74
  function configureWorkItemTools(server, tokenProvider, connectionProvider, userAgentProvider) {
67
75
  server.tool(WORKITEM_TOOLS.list_backlogs, "Receive a list of backlogs for a given project and team. If a project or team is not specified, you will be prompted to select one.", {
68
76
  project: z.string().optional().describe("The name or ID of the Azure DevOps project. Reuse from prior context if already known. If not provided, a project selection prompt will be shown."),
@@ -1057,6 +1065,16 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
1057
1065
  commitId: z.string().optional().describe("The commit SHA hash. Required when linkType is 'Fixed in Commit'."),
1058
1066
  pullRequestId: z.coerce.number().min(1).optional().describe("The pull request ID. Required when linkType is 'Pull Request'."),
1059
1067
  buildId: z.coerce.number().min(1).optional().describe("The build ID. Required when linkType is 'Build', 'Found in build', or 'Integrated in build'."),
1068
+ wikiId: z.string().optional().describe("The wiki ID (GUID). Required when linkType is 'Wiki'."),
1069
+ pageId: z.coerce
1070
+ .number()
1071
+ .min(1)
1072
+ .optional()
1073
+ .describe("The numeric wiki page ID from the browser URL (e.g., '98' in '.../wikis/Contoso.wiki/98/What-is-Contoso'). When provided for 'Wiki' links, the full page path is resolved automatically via the API. Takes precedence over 'pagePath'."),
1074
+ pagePath: z
1075
+ .string()
1076
+ .optional()
1077
+ .describe("The full wiki page path from the wiki root (e.g., '/Home/What-is-Contoso'). Required when linkType is 'Wiki' and 'pageId' is not provided. Must be the complete path, not just the page name from the URL."),
1060
1078
  linkType: z
1061
1079
  .enum([
1062
1080
  "Branch",
@@ -1077,7 +1095,7 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
1077
1095
  .default("Branch")
1078
1096
  .describe("Type of artifact link, defaults to 'Branch'. This determines both the link type and how to build the VSTFS URI from individual components."),
1079
1097
  comment: z.string().optional().describe("Comment to include with the artifact link."),
1080
- }, async ({ workItemId, project, artifactUri, projectId, repositoryId, branchName, commitId, pullRequestId, buildId, linkType, comment }) => {
1098
+ }, async ({ workItemId, project, artifactUri, projectId, repositoryId, branchName, commitId, pullRequestId, buildId, wikiId, pageId, pagePath, linkType, comment }) => {
1081
1099
  try {
1082
1100
  const connection = await connectionProvider();
1083
1101
  let resolvedProject = project;
@@ -1134,6 +1152,45 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
1134
1152
  }
1135
1153
  finalArtifactUri = `vstfs:///Build/Build/${encodeURIComponent(buildId.toString())}`;
1136
1154
  break;
1155
+ case "Wiki": {
1156
+ if (!projectId || !wikiId) {
1157
+ return {
1158
+ content: [{ type: "text", text: "For 'Wiki' links, 'projectId', 'wikiId', and 'pagePath' are required." }],
1159
+ isError: true,
1160
+ };
1161
+ }
1162
+ let resolvedPagePath = pagePath;
1163
+ if (pageId !== undefined) {
1164
+ // Look up the actual page path by page ID to get the full path
1165
+ const orgUrl = connection.serverUrl;
1166
+ const accessToken = await tokenProvider();
1167
+ const pageResponse = await fetch(`${orgUrl}/${encodeURIComponent(resolvedProject)}/_apis/wiki/wikis/${encodeURIComponent(wikiId)}/pages/${pageId}?api-version=7.1`, {
1168
+ headers: {
1169
+ "Authorization": `Bearer ${accessToken}`,
1170
+ "User-Agent": userAgentProvider(),
1171
+ },
1172
+ });
1173
+ if (!pageResponse.ok) {
1174
+ return {
1175
+ content: [{ type: "text", text: `Failed to look up wiki page ID ${pageId}: ${pageResponse.statusText}` }],
1176
+ isError: true,
1177
+ };
1178
+ }
1179
+ const pageData = await pageResponse.json();
1180
+ resolvedPagePath = pageData.path;
1181
+ }
1182
+ if (!resolvedPagePath) {
1183
+ return {
1184
+ content: [{ type: "text", text: "For 'Wiki' links, 'pageId' or 'pagePath' is required." }],
1185
+ isError: true,
1186
+ };
1187
+ }
1188
+ // Strip leading slash, then encode each segment joined by %2F
1189
+ const normalizedPath = resolvedPagePath.startsWith("/") ? resolvedPagePath.slice(1) : resolvedPagePath;
1190
+ const encodedPath = normalizedPath.split("/").map(encodeURIComponent).join("%2F");
1191
+ finalArtifactUri = `vstfs:///Wiki/WikiPage/${encodeURIComponent(projectId)}%2F${encodeURIComponent(wikiId)}%2F${encodedPath}`;
1192
+ break;
1193
+ }
1137
1194
  default:
1138
1195
  return {
1139
1196
  content: [{ type: "text", text: `URI building from components is not supported for link type '${linkType}'. Please provide the full 'artifactUri' instead.` }],
@@ -1150,7 +1207,7 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
1150
1207
  rel: "ArtifactLink",
1151
1208
  url: finalArtifactUri,
1152
1209
  attributes: {
1153
- name: linkType,
1210
+ name: getArtifactLinkAttributeName(linkType),
1154
1211
  ...(comment && { comment }),
1155
1212
  },
1156
1213
  },
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const packageVersion = "2.8.0-nightly.20260707";
1
+ export const packageVersion = "2.8.0-nightly.20260710";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@azure-devops/mcp",
3
- "version": "2.8.0-nightly.20260707",
3
+ "version": "2.8.0-nightly.20260710",
4
4
  "mcpName": "microsoft.com/azure-devops",
5
5
  "description": "MCP server for interacting with Azure DevOps",
6
6
  "license": "MIT",