@azure-devops/mcp 1.3.1-nightly.20250813 β 1.3.1-nightly.20250815
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 +23 -5
- package/dist/prompts.js +5 -5
- package/dist/tools/wiki.js +105 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,11 +33,25 @@ The Azure DevOps MCP Server brings Azure DevOps context to your agents. Try prom
|
|
|
33
33
|
- "List iterations for project 'Contoso'"
|
|
34
34
|
- "List my work items for project 'Contoso'"
|
|
35
35
|
- "List work items in current iteration for 'Contoso' project and 'Contoso Team'"
|
|
36
|
+
- "List all wikis in the 'Contoso' project"
|
|
37
|
+
- "Create a wiki page '/Architecture/Overview' with content about system design"
|
|
38
|
+
- "Update the wiki page '/Getting Started' with new onboarding instructions"
|
|
39
|
+
- "Get the content of the wiki page '/API/Authentication' from the Documentation wiki"
|
|
36
40
|
|
|
37
41
|
## π Expectations
|
|
38
42
|
|
|
39
43
|
The Azure DevOps MCP Server is built from tools that are concise, simple, focused, and easy to useβeach designed for a specific scenario. We intentionally avoid complex tools that try to do too much. The goal is to provide a thin abstraction layer over the REST APIs, making data access straightforward and letting the language model handle complex reasoning.
|
|
40
44
|
|
|
45
|
+
## β¨ Recent Enhancements
|
|
46
|
+
|
|
47
|
+
### π **Enhanced Wiki Support**
|
|
48
|
+
|
|
49
|
+
- **Full Content Management**: Create and update wiki pages with complete content using the native Azure DevOps REST API
|
|
50
|
+
- **Automatic ETag Handling**: Safe updates with built-in conflict resolution for concurrent edits
|
|
51
|
+
- **Immediate Visibility**: Pages appear instantly in the Azure DevOps wiki interface
|
|
52
|
+
- **Hierarchical Structure**: Support for organized page structures within existing folder hierarchies
|
|
53
|
+
- **Robust Error Handling**: Comprehensive error management for various HTTP status codes and edge cases
|
|
54
|
+
|
|
41
55
|
## βοΈ Supported Tools
|
|
42
56
|
|
|
43
57
|
Interact with these Azure DevOps services:
|
|
@@ -74,11 +88,7 @@ Interact with these Azure DevOps services:
|
|
|
74
88
|
- **wit_update_work_items_batch**: Update work items in batch.
|
|
75
89
|
- **wit_work_items_link**: Link work items together in batch.
|
|
76
90
|
- **wit_work_item_unlink**: Unlink one or many links from a work item.
|
|
77
|
-
|
|
78
|
-
#### Deprecated Tools
|
|
79
|
-
|
|
80
|
-
- **wit_add_child_work_item**: Replaced by `wit_add_child_work_items` to allow creating one or more child items per call.
|
|
81
|
-
- **wit_close_and_link_workitem_duplicates**: This tool is no longer needed. Finding and marking duplicates can be done with other tools.
|
|
91
|
+
- **wit_add_artifact_link**: Link to artifacts like branch, pull request, commit, and build.
|
|
82
92
|
|
|
83
93
|
### π Repositories
|
|
84
94
|
|
|
@@ -133,6 +143,14 @@ Interact with these Azure DevOps services:
|
|
|
133
143
|
- **testplan_list_test_cases**: Get a list of test cases in the test plan.
|
|
134
144
|
- **testplan_show_test_results_from_build_id**: Get a list of test results for a given project and build ID.
|
|
135
145
|
|
|
146
|
+
### π Wiki
|
|
147
|
+
|
|
148
|
+
- **wiki_list_wikis**: Retrieve a list of wikis for an organization or project.
|
|
149
|
+
- **wiki_get_wiki**: Get the wiki by wikiIdentifier.
|
|
150
|
+
- **wiki_list_pages**: Retrieve a list of wiki pages for a specific wiki and project.
|
|
151
|
+
- **wiki_get_page_content**: Retrieve wiki page content by wikiIdentifier and path.
|
|
152
|
+
- **wiki_create_or_update_page**: Create or update wiki pages with full content support.
|
|
153
|
+
|
|
136
154
|
### π Search
|
|
137
155
|
|
|
138
156
|
- **search_code**: Get code search results for a given search text.
|
package/dist/prompts.js
CHANGED
|
@@ -4,7 +4,7 @@ import { z } from "zod";
|
|
|
4
4
|
import { CORE_TOOLS } from "./tools/core.js";
|
|
5
5
|
import { WORKITEM_TOOLS } from "./tools/workitems.js";
|
|
6
6
|
function configurePrompts(server) {
|
|
7
|
-
server.prompt("
|
|
7
|
+
server.prompt("Projects", "Lists all projects in the Azure DevOps organization.", {}, () => ({
|
|
8
8
|
messages: [
|
|
9
9
|
{
|
|
10
10
|
role: "user",
|
|
@@ -12,13 +12,13 @@ function configurePrompts(server) {
|
|
|
12
12
|
type: "text",
|
|
13
13
|
text: String.raw `
|
|
14
14
|
# Task
|
|
15
|
-
Use the '${CORE_TOOLS.list_projects}' tool to retrieve all projects in the current Azure DevOps organization.
|
|
16
|
-
Present the results in a table with the following columns:
|
|
15
|
+
Use the '${CORE_TOOLS.list_projects}' tool to retrieve all 'wellFormed' projects in the current Azure DevOps organization.
|
|
16
|
+
Present the results in alphabetical order in a table with the following columns: Name and ID.`,
|
|
17
17
|
},
|
|
18
18
|
},
|
|
19
19
|
],
|
|
20
20
|
}));
|
|
21
|
-
server.prompt("
|
|
21
|
+
server.prompt("Teams", "Retrieves all teams for a given Azure DevOps project.", { project: z.string() }, ({ project }) => ({
|
|
22
22
|
messages: [
|
|
23
23
|
{
|
|
24
24
|
role: "user",
|
|
@@ -27,7 +27,7 @@ Present the results in a table with the following columns: Project ID, Name, and
|
|
|
27
27
|
text: String.raw `
|
|
28
28
|
# Task
|
|
29
29
|
Use the '${CORE_TOOLS.list_project_teams}' tool to retrieve all teams for the project '${project}'.
|
|
30
|
-
Present the results in a table with the following columns:
|
|
30
|
+
Present the results in alphabetical order in a table with the following columns: Name and Id`,
|
|
31
31
|
},
|
|
32
32
|
},
|
|
33
33
|
],
|
package/dist/tools/wiki.js
CHANGED
|
@@ -6,6 +6,7 @@ const WIKI_TOOLS = {
|
|
|
6
6
|
get_wiki: "wiki_get_wiki",
|
|
7
7
|
list_wiki_pages: "wiki_list_pages",
|
|
8
8
|
get_wiki_page_content: "wiki_get_page_content",
|
|
9
|
+
create_or_update_page: "wiki_create_or_update_page",
|
|
9
10
|
};
|
|
10
11
|
function configureWikiTools(server, tokenProvider, connectionProvider) {
|
|
11
12
|
server.tool(WIKI_TOOLS.get_wiki, "Get the wiki by wikiIdentifier", {
|
|
@@ -109,6 +110,110 @@ function configureWikiTools(server, tokenProvider, connectionProvider) {
|
|
|
109
110
|
};
|
|
110
111
|
}
|
|
111
112
|
});
|
|
113
|
+
server.tool(WIKI_TOOLS.create_or_update_page, "Create or update a wiki page with content.", {
|
|
114
|
+
wikiIdentifier: z.string().describe("The unique identifier or name of the wiki."),
|
|
115
|
+
path: z.string().describe("The path of the wiki page (e.g., '/Home' or '/Documentation/Setup')."),
|
|
116
|
+
content: z.string().describe("The content of the wiki page in markdown format."),
|
|
117
|
+
project: z.string().optional().describe("The project name or ID where the wiki is located. If not provided, the default project will be used."),
|
|
118
|
+
etag: z.string().optional().describe("ETag for editing existing pages (optional, will be fetched if not provided)."),
|
|
119
|
+
}, async ({ wikiIdentifier, path, content, project, etag }) => {
|
|
120
|
+
try {
|
|
121
|
+
const connection = await connectionProvider();
|
|
122
|
+
const accessToken = await tokenProvider();
|
|
123
|
+
// Normalize the path
|
|
124
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
125
|
+
const encodedPath = encodeURIComponent(normalizedPath);
|
|
126
|
+
// Build the URL for the wiki page API
|
|
127
|
+
const baseUrl = connection.serverUrl;
|
|
128
|
+
const projectParam = project || "";
|
|
129
|
+
const url = `${baseUrl}/${projectParam}/_apis/wiki/wikis/${wikiIdentifier}/pages?path=${encodedPath}&api-version=7.1`;
|
|
130
|
+
// First, try to create a new page (PUT without ETag)
|
|
131
|
+
try {
|
|
132
|
+
const createResponse = await fetch(url, {
|
|
133
|
+
method: "PUT",
|
|
134
|
+
headers: {
|
|
135
|
+
"Authorization": `Bearer ${accessToken.token}`,
|
|
136
|
+
"Content-Type": "application/json",
|
|
137
|
+
},
|
|
138
|
+
body: JSON.stringify({ content: content }),
|
|
139
|
+
});
|
|
140
|
+
if (createResponse.ok) {
|
|
141
|
+
const result = await createResponse.json();
|
|
142
|
+
return {
|
|
143
|
+
content: [
|
|
144
|
+
{
|
|
145
|
+
type: "text",
|
|
146
|
+
text: `Successfully created wiki page at path: ${normalizedPath}. Response: ${JSON.stringify(result, null, 2)}`,
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
// If creation failed with 409 (Conflict) or 500 (Page exists), try to update it
|
|
152
|
+
if (createResponse.status === 409 || createResponse.status === 500) {
|
|
153
|
+
// Page exists, we need to get the ETag and update it
|
|
154
|
+
let currentEtag = etag;
|
|
155
|
+
if (!currentEtag) {
|
|
156
|
+
// Fetch current page to get ETag
|
|
157
|
+
const getResponse = await fetch(url, {
|
|
158
|
+
method: "GET",
|
|
159
|
+
headers: {
|
|
160
|
+
Authorization: `Bearer ${accessToken.token}`,
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
if (getResponse.ok) {
|
|
164
|
+
currentEtag = getResponse.headers.get("etag") || getResponse.headers.get("ETag") || undefined;
|
|
165
|
+
if (!currentEtag) {
|
|
166
|
+
const pageData = await getResponse.json();
|
|
167
|
+
currentEtag = pageData.eTag;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (!currentEtag) {
|
|
171
|
+
throw new Error("Could not retrieve ETag for existing page");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Now update the existing page with ETag
|
|
175
|
+
const updateResponse = await fetch(url, {
|
|
176
|
+
method: "PUT",
|
|
177
|
+
headers: {
|
|
178
|
+
"Authorization": `Bearer ${accessToken.token}`,
|
|
179
|
+
"Content-Type": "application/json",
|
|
180
|
+
"If-Match": currentEtag,
|
|
181
|
+
},
|
|
182
|
+
body: JSON.stringify({ content: content }),
|
|
183
|
+
});
|
|
184
|
+
if (updateResponse.ok) {
|
|
185
|
+
const result = await updateResponse.json();
|
|
186
|
+
return {
|
|
187
|
+
content: [
|
|
188
|
+
{
|
|
189
|
+
type: "text",
|
|
190
|
+
text: `Successfully updated wiki page at path: ${normalizedPath}. Response: ${JSON.stringify(result, null, 2)}`,
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
const errorText = await updateResponse.text();
|
|
197
|
+
throw new Error(`Failed to update page (${updateResponse.status}): ${errorText}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
const errorText = await createResponse.text();
|
|
202
|
+
throw new Error(`Failed to create page (${createResponse.status}): ${errorText}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
catch (fetchError) {
|
|
206
|
+
throw fetchError;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
211
|
+
return {
|
|
212
|
+
content: [{ type: "text", text: `Error creating/updating wiki page: ${errorMessage}` }],
|
|
213
|
+
isError: true,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
});
|
|
112
217
|
}
|
|
113
218
|
function streamToString(stream) {
|
|
114
219
|
return new Promise((resolve, reject) => {
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const packageVersion = "1.3.1-nightly.
|
|
1
|
+
export const packageVersion = "1.3.1-nightly.20250815";
|