@azure-devops/mcp 2.10.0-nightly.20260930 → 2.10.0-nightly.20261002
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 +3 -8
- package/dist/tools/pipelines.js +4 -2
- package/dist/tools/repositories.js +2 -2
- package/dist/tools/wiki.js +7 -7
- package/dist/tools/work-items.js +11 -6
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
# Azure DevOps MCP Server
|
|
2
2
|
|
|
3
|
-
> [!
|
|
4
|
-
> We
|
|
3
|
+
> [!IMPORTANT]
|
|
4
|
+
> We recommend using the [Remote MCP Server](https://learn.microsoft.com/en-us/azure/devops/mcp-server/remote-mcp-server) instead of this local server. It requires no installation and gets new features first.
|
|
5
5
|
>
|
|
6
|
-
>
|
|
6
|
+
> [Learn more](#remote-mcp-server-recommended)
|
|
7
7
|
|
|
8
8
|
This project gives AI agents access to Azure DevOps through the Model Context Protocol (MCP). Use the hosted remote server for the simplest setup, or run the local server when you need a `stdio` connection.
|
|
9
9
|
|
|
10
10
|
## Table of Contents
|
|
11
11
|
|
|
12
|
-
> [!IMPORTANT]
|
|
13
|
-
> We recommend using the [Remote MCP Server](https://learn.microsoft.com/en-us/azure/devops/mcp-server/remote-mcp-server) instead of this local server. It requires no installation and gets new features first.
|
|
14
|
-
>
|
|
15
|
-
> [Learn more](#remote-mcp-server-recommended)
|
|
16
|
-
|
|
17
12
|
1. [Overview](#overview)
|
|
18
13
|
2. [Design](#design)
|
|
19
14
|
3. [Remote MCP Server (Recommended)](#remote-mcp-server-recommended)
|
package/dist/tools/pipelines.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import { apiVersion, getEnumKeys, safeEnumConvert } from "../utils.js";
|
|
4
4
|
import { BuildQueryOrder, DefinitionQueryOrder } from "azure-devops-node-api/interfaces/BuildInterfaces.js";
|
|
5
5
|
import { z } from "zod";
|
|
6
|
+
const buildQueryOrderKeys = getEnumKeys(BuildQueryOrder);
|
|
7
|
+
const definitionQueryOrderKeys = getEnumKeys(DefinitionQueryOrder);
|
|
6
8
|
import { StageUpdateType } from "azure-devops-node-api/interfaces/BuildInterfaces.js";
|
|
7
9
|
import { ConfigurationType, RepositoryType } from "azure-devops-node-api/interfaces/PipelinesInterfaces.js";
|
|
8
10
|
import { mkdirSync, createWriteStream } from "fs";
|
|
@@ -153,7 +155,7 @@ function configurePipelineTools(server, tokenProvider, connectionProvider, userA
|
|
|
153
155
|
continuationToken: z.string().optional().describe("Token for continuing paged results. Used for: list, get_changes."),
|
|
154
156
|
maxBuildsPerDefinition: z.number().optional().describe("Maximum number of builds per definition. Used for: list."),
|
|
155
157
|
deletedFilter: z.number().optional().describe("Filter for deleted builds (see QueryDeletedOption enum). Used for: list."),
|
|
156
|
-
queryOrder: z.
|
|
158
|
+
queryOrder: z.enum(buildQueryOrderKeys).optional().describe("Order in which builds are returned. Used for: list."),
|
|
157
159
|
branchName: z.string().optional().describe("Branch name to filter builds. Used for: list."),
|
|
158
160
|
buildIds: z.array(z.coerce.number().min(1)).optional().describe("Array of specific build IDs to retrieve. Used for: list."),
|
|
159
161
|
repositoryId: z.string().optional().describe("Repository ID to filter builds. Used for: list."),
|
|
@@ -237,7 +239,7 @@ function configurePipelineTools(server, tokenProvider, connectionProvider, userA
|
|
|
237
239
|
repositoryType: z.enum(["TfsGit", "GitHub", "BitbucketCloud"]).optional().describe("Repository type to filter definitions. Used for: list."),
|
|
238
240
|
name: z.string().optional().describe("Name filter for build definitions. Used for: list."),
|
|
239
241
|
path: z.string().optional().describe("Path filter for build definitions. Used for: list."),
|
|
240
|
-
queryOrder: z.
|
|
242
|
+
queryOrder: z.enum(definitionQueryOrderKeys).optional().describe("Order in which definitions are returned. Used for: list."),
|
|
241
243
|
top: z.number().optional().describe("Maximum number of definitions to return. Used for: list."),
|
|
242
244
|
continuationToken: z.string().optional().describe("Token for continuing paged results. Used for: list."),
|
|
243
245
|
minMetricsTime: z.coerce.date().optional().describe("Minimum metrics time to filter definitions. Used for: list."),
|
|
@@ -899,8 +899,8 @@ function configureRepoTools(server, tokenProvider, connectionProvider, userAgent
|
|
|
899
899
|
fullResponse: z.boolean().optional().default(false).describe("Return full JSON response. Used for reply and update."),
|
|
900
900
|
rightFileStartLine: z.coerce.number().min(1).optional().describe("Start line in the right file. Used for create."),
|
|
901
901
|
rightFileStartOffset: z.number().optional().describe("Start character offset in the right file. Used for create."),
|
|
902
|
-
rightFileEndLine: z.number().optional().describe("End line in the right file. Used for create."),
|
|
903
|
-
rightFileEndOffset: z.number().optional().describe("End character offset in the right file. Used for create."),
|
|
902
|
+
rightFileEndLine: z.number().optional().describe("End line in the right file. Used for create. Must be supplied together with rightFileEndOffset."),
|
|
903
|
+
rightFileEndOffset: z.number().optional().describe("End character offset in the right file. Used for create. Must be supplied together with rightFileEndLine."),
|
|
904
904
|
changeTrackingId: z.coerce.number().int().min(1).optional().describe("The file change tracking ID from the pull request iteration changes. Used for create."),
|
|
905
905
|
firstComparingIteration: z.coerce.number().int().min(0).optional().describe("The iteration on the left side of the diff. Used for create."),
|
|
906
906
|
secondComparingIteration: z.coerce.number().int().min(1).optional().describe("The iteration on the right side of the diff. Used for create."),
|
package/dist/tools/wiki.js
CHANGED
|
@@ -346,18 +346,18 @@ function parseWikiUrl(url) {
|
|
|
346
346
|
if (idx < 1 || segments[idx + 1] !== "wikis") {
|
|
347
347
|
return { error: "URL does not match expected wiki pattern (missing /_wiki/wikis/ segment)." };
|
|
348
348
|
}
|
|
349
|
-
const
|
|
350
|
-
const
|
|
351
|
-
if (!
|
|
349
|
+
const encodedProject = segments[idx - 1];
|
|
350
|
+
const encodedWikiIdentifier = segments[idx + 2];
|
|
351
|
+
if (!encodedProject || !encodedWikiIdentifier) {
|
|
352
352
|
return { error: "Could not extract project or wikiIdentifier from URL." };
|
|
353
353
|
}
|
|
354
|
+
const project = decodeURIComponent(encodedProject);
|
|
355
|
+
const wikiIdentifier = decodeURIComponent(encodedWikiIdentifier);
|
|
354
356
|
// Query form with pagePath
|
|
355
357
|
const pagePathParam = u.searchParams.get("pagePath");
|
|
356
358
|
if (pagePathParam) {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
decoded = "/" + decoded;
|
|
360
|
-
return { project, wikiIdentifier, pagePath: decoded };
|
|
359
|
+
const pagePath = pagePathParam.startsWith("/") ? pagePathParam : `/${pagePathParam}`;
|
|
360
|
+
return { project, wikiIdentifier, pagePath };
|
|
361
361
|
}
|
|
362
362
|
// Path ID form: .../wikis/{wikiIdentifier}/{pageId}/...
|
|
363
363
|
const afterWiki = segments.slice(idx + 3); // elements after wikiIdentifier
|
package/dist/tools/work-items.js
CHANGED
|
@@ -71,10 +71,8 @@ function escapeHtml(value) {
|
|
|
71
71
|
};
|
|
72
72
|
return value.replace(/[&<>"']/g, (character) => entities[character]);
|
|
73
73
|
}
|
|
74
|
-
async function
|
|
74
|
+
async function prepareCommentText(text, format, tokenProvider, connectionProvider, userAgentProvider) {
|
|
75
75
|
const emailMatches = [...text.matchAll(/@<([^<>\s]+@[^<>\s]+)>/g)];
|
|
76
|
-
if (emailMatches.length === 0)
|
|
77
|
-
return text;
|
|
78
76
|
const identities = new Map();
|
|
79
77
|
for (const email of new Set(emailMatches.map((match) => match[1]))) {
|
|
80
78
|
try {
|
|
@@ -84,11 +82,18 @@ async function resolveCommentMentions(text, format, tokenProvider, connectionPro
|
|
|
84
82
|
// Leave mentions unchanged when their identities cannot be resolved.
|
|
85
83
|
}
|
|
86
84
|
}
|
|
87
|
-
|
|
85
|
+
// Encode Markdown transport text once, preserving mention tokens for Azure DevOps.
|
|
86
|
+
// Encoding ampersands also preserves intentional entity text after one decode.
|
|
87
|
+
const markdown = format === "Markdown" || format === undefined;
|
|
88
|
+
return text.replace(/@<([^<>\s]+@[^<>\s]+)>|@<([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})>|[&<>]/gi, (mention, email, id) => {
|
|
89
|
+
if (id)
|
|
90
|
+
return mention;
|
|
91
|
+
if (!email)
|
|
92
|
+
return markdown ? escapeHtml(mention) : mention;
|
|
88
93
|
const identity = identities.get(email);
|
|
89
94
|
if (!identity)
|
|
90
95
|
return escapeHtml(mention);
|
|
91
|
-
return
|
|
96
|
+
return markdown ? `@<${identity.id}>` : `<a href="#" data-vss-mention="version:2.0,${identity.id}">@${escapeHtml(identity.displayName)}</a>`;
|
|
92
97
|
});
|
|
93
98
|
}
|
|
94
99
|
function configureWorkItemTools(server, tokenProvider, connectionProvider, userAgentProvider) {
|
|
@@ -841,7 +846,7 @@ function configureWorkItemTools(server, tokenProvider, connectionProvider, userA
|
|
|
841
846
|
const orgUrl = connection.serverUrl;
|
|
842
847
|
const accessToken = await tokenProvider();
|
|
843
848
|
const formatParameter = (format ?? "Markdown") === "Markdown" ? 0 : 1;
|
|
844
|
-
const resolvedText = await
|
|
849
|
+
const resolvedText = await prepareCommentText(text, format, tokenProvider, connectionProvider, userAgentProvider);
|
|
845
850
|
if (action === "add") {
|
|
846
851
|
const response = await fetch(`${orgUrl}/${encodeURIComponent(resolvedProject)}/_apis/wit/workItems/${workItemId}/comments?format=${formatParameter}&api-version=${markdownCommentsApiVersion}`, {
|
|
847
852
|
method: "POST",
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const packageVersion = "2.10.0-nightly.
|
|
1
|
+
export const packageVersion = "2.10.0-nightly.20261002";
|
package/package.json
CHANGED