@mcp-apps/azure-devops-mcp-server
Version:
A Model Context Protocol (MCP) server for Azure DevOps integration
267 lines (261 loc) • 12.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.updateWorkItemTool = exports.createWorkItemTool = exports.listWorkItemsTool = void 0;
const zod_1 = require("zod");
const azure_devops_client_1 = require("../utils/azure-devops-client");
// Tool to list work items
exports.listWorkItemsTool = {
name: "list-work-items",
description: `
List work items in Azure DevOps.
This tool accepts a WIQL query to filter work items. WIQL (Work Item Query Language) is
similar to SQL and allows querying work items based on their fields.
If you have a work item URL like 'https://dev.azure.com/{organization}/{project}/_workitems/edit/{id}',
you can extract the work item ID from the URL and use it in a query like:
'SELECT [System.Id] FROM workitems WHERE [System.Id] = {id}'
`,
parameters: {
organizationUrl: zod_1.z.string().describe("Azure DevOps organization URL (e.g., https://dev.azure.com/organization)"),
project: zod_1.z.string().describe("Project name"),
query: zod_1.z.string().describe("Work item query (e.g., SELECT [System.Id] FROM workitems WHERE [System.WorkItemType] = 'Bug')"),
},
handler: async ({ organizationUrl, project, query }) => {
try {
const witApi = await (0, azure_devops_client_1.getWorkItemTrackingApi)(organizationUrl);
// Execute the query
const queryResult = await witApi.queryByWiql({ query }, { project }, false, 10);
if (!queryResult || !queryResult.workItems || queryResult.workItems.length === 0) {
return {
content: [{ type: "text", text: "No work items found matching the query." }],
};
}
const workItemIds = queryResult.workItems.map((wi) => wi.id).filter((id) => id !== undefined);
const workItems = await witApi.getWorkItems(workItemIds);
const formattedItems = workItems.map((item) => {
const fields = item.fields || {};
return {
id: item.id,
type: fields["System.WorkItemType"] || "Unknown",
title: fields["System.Title"] || "No Title",
state: fields["System.State"] || "Unknown",
description: fields["System.Description"] || "No Description",
createdBy: fields["System.CreatedBy"]?.displayName || "Unknown",
assignedTo: fields["System.AssignedTo"]?.displayName || "Unassigned",
url: item._links?.html?.href || `https://dev.azure.com/${organizationUrl.split("/")[3]}/${project}/_workitems/edit/${item.id}`,
};
});
return {
content: [{ type: "text", text: JSON.stringify(formattedItems, null, 2) }],
};
}
catch (error) {
console.error("Error listing work items:", error);
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [{ type: "text", text: `Error listing work items: ${errorMessage}` }],
};
}
}
};
// Tool to create a work item
exports.createWorkItemTool = {
name: "create-work-item",
description: `
Creates a new work item in an Azure DevOps project. If the fields are required, they must be provided.
Confirm with the user if any of the required fields are missing.
Parameters:
- organizationUrl [Required]: Azure DevOps URL (https://dev.azure.com/{organization})
- project [Required]: Project name (case-sensitive)
- areaPath: [Required] Area path (e.g., TeamProject\\Area) - case-sensitive
- iterationPath: [Required] Iteration path (e.g., TeamProject\\Iteration) - case-sensitive
- workItemType [Required]: Type of work item (Bug, Task, User Story, etc.) - case-sensitive
- title [Required]: Title of the work item
- description [Optional]: Detailed description (supports HTML)
- assignedTo [Optional]: User email or display name
Make sure to provide link to the user in appropriate format.
`,
parameters: {
organizationUrl: zod_1.z.string().describe("Azure DevOps organization URL (e.g., https://dev.azure.com/organization)"),
project: zod_1.z.string().describe("Project name"),
areaPath: zod_1.z.string().describe("Area path (e.g., TeamProject\\Area)"),
iterationPath: zod_1.z.string().describe("Iteration path (e.g., TeamProject\\Iteration)"),
workItemType: zod_1.z.string().describe("Work item type (e.g., Bug, Task, User Story)"),
title: zod_1.z.string().describe("Work item title"),
description: zod_1.z.string().optional().describe("Work item description"),
assignedTo: zod_1.z.string().optional().describe("User to assign the work item to"),
},
handler: async ({ organizationUrl, project, areaPath, iterationPath, workItemType, title, description, assignedTo }) => {
try {
const witApi = await (0, azure_devops_client_1.getWorkItemTrackingApi)(organizationUrl);
const patchDocument = [
{
op: "add",
path: "/fields/System.Title",
value: title,
},
];
if (description) {
patchDocument.push({
op: "add",
path: "/fields/System.Description",
value: description,
});
}
if (assignedTo) {
patchDocument.push({
op: "add",
path: "/fields/System.AssignedTo",
value: assignedTo,
});
}
if (areaPath) {
patchDocument.push({
op: "add",
path: "/fields/System.AreaPath",
value: areaPath,
});
}
if (iterationPath) {
patchDocument.push({
op: "add",
path: "/fields/System.IterationPath",
value: iterationPath,
});
}
patchDocument.push({
op: "add",
path: "/fields/System.Tags",
value: "Created by AI",
});
// Create the work item
const createdWorkItem = await witApi.createWorkItem(null, patchDocument, project, workItemType);
const fields = createdWorkItem.fields || {};
return {
content: [
{
type: "text",
text: `Work item created successfully:\nID: ${createdWorkItem.id}\nTitle: ${fields["System.Title"] || workItemType}\nType: ${workItemType}\nURL: ${createdWorkItem._links?.html?.href || ""}`,
},
],
};
}
catch (error) {
console.error("Error creating work item:", error);
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [{ type: "text", text: `Error creating work item: ${errorMessage}` }],
};
}
}
};
// Tool to update a work item
exports.updateWorkItemTool = {
name: "update-work-item",
description: `
Updates an existing work item in Azure DevOps.
Parameters:
- organizationUrl [Required]: Azure DevOps URL (https://dev.azure.com/{organization})
- project [Required]: Project name (case-sensitive)
- id [Required]: Work item ID to update
- title [Optional]: Updated work item title
- description [Optional]: Updated work item description
- state [Optional]: Updated work item state (e.g., Active, Resolved, Closed)
- assignedTo [Optional]: User email or display name to assign the work item to
- areaPath [Optional]: Updated area path
- iterationPath [Optional]: Updated iteration path
Specify only the fields you want to update. At least one field other than organizationUrl,
project, and id must be provided.
`,
parameters: {
organizationUrl: zod_1.z.string().describe("Azure DevOps organization URL (e.g., https://dev.azure.com/organization)"),
project: zod_1.z.string().describe("Project name"),
id: zod_1.z.number().describe("Work item ID to update"),
title: zod_1.z.string().optional().describe("Updated work item title"),
description: zod_1.z.string().optional().describe("Updated work item description"),
state: zod_1.z.string().optional().describe("Updated work item state (e.g., Active, Resolved, Closed)"),
assignedTo: zod_1.z.string().optional().describe("User to assign the work item to"),
areaPath: zod_1.z.string().optional().describe("Updated area path"),
iterationPath: zod_1.z.string().optional().describe("Updated iteration path"),
},
handler: async ({ organizationUrl, project, id, title, description, state, assignedTo, areaPath, iterationPath }) => {
try {
// Ensure at least one update field is provided
if (!title && !description && !state && !assignedTo && !areaPath && !iterationPath) {
return {
content: [{
type: "text",
text: "Error: At least one field to update must be provided (title, description, state, assignedTo, areaPath, or iterationPath)."
}],
};
}
const witApi = await (0, azure_devops_client_1.getWorkItemTrackingApi)(organizationUrl);
const patchDocument = [];
if (title) {
patchDocument.push({
op: "add",
path: "/fields/System.Title",
value: title,
});
}
if (description) {
patchDocument.push({
op: "add",
path: "/fields/System.Description",
value: description,
});
}
if (state) {
patchDocument.push({
op: "add",
path: "/fields/System.State",
value: state,
});
}
if (assignedTo) {
patchDocument.push({
op: "add",
path: "/fields/System.AssignedTo",
value: assignedTo,
});
}
if (areaPath) {
patchDocument.push({
op: "add",
path: "/fields/System.AreaPath",
value: areaPath,
});
}
if (iterationPath) {
patchDocument.push({
op: "add",
path: "/fields/System.IterationPath",
value: iterationPath,
});
}
patchDocument.push({
op: "add",
path: "/fields/System.ChangedBy",
value: "Updated by AI",
});
// Update the work item
const updatedWorkItem = await witApi.updateWorkItem(null, patchDocument, id, project);
const fields = updatedWorkItem.fields || {};
return {
content: [
{
type: "text",
text: `Work item updated successfully:\nID: ${updatedWorkItem.id}\nTitle: ${fields["System.Title"] || "Unknown"}\nState: ${fields["System.State"] || "Unknown"}\nURL: ${updatedWorkItem._links?.html?.href || ""}`,
},
],
};
}
catch (error) {
console.error("Error updating work item:", error);
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [{ type: "text", text: `Error updating work item: ${errorMessage}` }],
};
}
}
};
//# sourceMappingURL=work-items.js.map