@azure-devops/mcp 1.3.1-nightly.20250813 β 1.3.1-nightly.20250814
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 +22 -0
- package/dist/tools/wiki.js +106 -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:
|
|
@@ -133,6 +147,14 @@ Interact with these Azure DevOps services:
|
|
|
133
147
|
- **testplan_list_test_cases**: Get a list of test cases in the test plan.
|
|
134
148
|
- **testplan_show_test_results_from_build_id**: Get a list of test results for a given project and build ID.
|
|
135
149
|
|
|
150
|
+
### π Wiki
|
|
151
|
+
|
|
152
|
+
- **wiki_list_wikis**: Retrieve a list of wikis for an organization or project.
|
|
153
|
+
- **wiki_get_wiki**: Get the wiki by wikiIdentifier.
|
|
154
|
+
- **wiki_list_pages**: Retrieve a list of wiki pages for a specific wiki and project.
|
|
155
|
+
- **wiki_get_page_content**: Retrieve wiki page content by wikiIdentifier and path.
|
|
156
|
+
- **wiki_create_or_update_page**: β¨ **Enhanced** - Create or update wiki pages with full content support using Azure DevOps REST API. Features automatic ETag handling for safe updates, immediate content visibility, and proper conflict resolution.
|
|
157
|
+
|
|
136
158
|
### π Search
|
|
137
159
|
|
|
138
160
|
- **search_code**: Get code search results for a given search text.
|
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,111 @@ 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
|
+
comment: z.string().optional().describe("Optional comment for the page update."),
|
|
119
|
+
etag: z.string().optional().describe("ETag for editing existing pages (optional, will be fetched if not provided)."),
|
|
120
|
+
}, async ({ wikiIdentifier, path, content, project, etag }) => {
|
|
121
|
+
try {
|
|
122
|
+
const connection = await connectionProvider();
|
|
123
|
+
const accessToken = await tokenProvider();
|
|
124
|
+
// Normalize the path
|
|
125
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
126
|
+
const encodedPath = encodeURIComponent(normalizedPath);
|
|
127
|
+
// Build the URL for the wiki page API
|
|
128
|
+
const baseUrl = connection.serverUrl;
|
|
129
|
+
const projectParam = project || "";
|
|
130
|
+
const url = `${baseUrl}/${projectParam}/_apis/wiki/wikis/${wikiIdentifier}/pages?path=${encodedPath}&api-version=7.1`;
|
|
131
|
+
// First, try to create a new page (PUT without ETag)
|
|
132
|
+
try {
|
|
133
|
+
const createResponse = await fetch(url, {
|
|
134
|
+
method: "PUT",
|
|
135
|
+
headers: {
|
|
136
|
+
"Authorization": `Bearer ${accessToken.token}`,
|
|
137
|
+
"Content-Type": "application/json",
|
|
138
|
+
},
|
|
139
|
+
body: JSON.stringify({ content: content }),
|
|
140
|
+
});
|
|
141
|
+
if (createResponse.ok) {
|
|
142
|
+
const result = await createResponse.json();
|
|
143
|
+
return {
|
|
144
|
+
content: [
|
|
145
|
+
{
|
|
146
|
+
type: "text",
|
|
147
|
+
text: `Successfully created wiki page at path: ${normalizedPath}. Response: ${JSON.stringify(result, null, 2)}`,
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
// If creation failed with 409 (Conflict) or 500 (Page exists), try to update it
|
|
153
|
+
if (createResponse.status === 409 || createResponse.status === 500) {
|
|
154
|
+
// Page exists, we need to get the ETag and update it
|
|
155
|
+
let currentEtag = etag;
|
|
156
|
+
if (!currentEtag) {
|
|
157
|
+
// Fetch current page to get ETag
|
|
158
|
+
const getResponse = await fetch(url, {
|
|
159
|
+
method: "GET",
|
|
160
|
+
headers: {
|
|
161
|
+
Authorization: `Bearer ${accessToken.token}`,
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
if (getResponse.ok) {
|
|
165
|
+
currentEtag = getResponse.headers.get("etag") || getResponse.headers.get("ETag") || undefined;
|
|
166
|
+
if (!currentEtag) {
|
|
167
|
+
const pageData = await getResponse.json();
|
|
168
|
+
currentEtag = pageData.eTag;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (!currentEtag) {
|
|
172
|
+
throw new Error("Could not retrieve ETag for existing page");
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// Now update the existing page with ETag
|
|
176
|
+
const updateResponse = await fetch(url, {
|
|
177
|
+
method: "PUT",
|
|
178
|
+
headers: {
|
|
179
|
+
"Authorization": `Bearer ${accessToken.token}`,
|
|
180
|
+
"Content-Type": "application/json",
|
|
181
|
+
"If-Match": currentEtag,
|
|
182
|
+
},
|
|
183
|
+
body: JSON.stringify({ content: content }),
|
|
184
|
+
});
|
|
185
|
+
if (updateResponse.ok) {
|
|
186
|
+
const result = await updateResponse.json();
|
|
187
|
+
return {
|
|
188
|
+
content: [
|
|
189
|
+
{
|
|
190
|
+
type: "text",
|
|
191
|
+
text: `Successfully updated wiki page at path: ${normalizedPath}. Response: ${JSON.stringify(result, null, 2)}`,
|
|
192
|
+
},
|
|
193
|
+
],
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
const errorText = await updateResponse.text();
|
|
198
|
+
throw new Error(`Failed to update page (${updateResponse.status}): ${errorText}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
const errorText = await createResponse.text();
|
|
203
|
+
throw new Error(`Failed to create page (${createResponse.status}): ${errorText}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
catch (fetchError) {
|
|
207
|
+
throw fetchError;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
212
|
+
return {
|
|
213
|
+
content: [{ type: "text", text: `Error creating/updating wiki page: ${errorMessage}` }],
|
|
214
|
+
isError: true,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
});
|
|
112
218
|
}
|
|
113
219
|
function streamToString(stream) {
|
|
114
220
|
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.20250814";
|