Skip to content

Commit 29b37cf

Browse files
committed
feat: add excludedIds parameter to filter iterations in list_iterations tool
1 parent 80dc6c6 commit 29b37cf

File tree

2 files changed

+396
-4
lines changed

2 files changed

+396
-4
lines changed

src/tools/work.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
55
import { WebApi } from "azure-devops-node-api";
66
import { z } from "zod";
7-
import { TreeStructureGroup, TreeNodeStructureType } from "azure-devops-node-api/interfaces/WorkItemTrackingInterfaces.js";
7+
import { TreeStructureGroup, TreeNodeStructureType, WorkItemClassificationNode } from "azure-devops-node-api/interfaces/WorkItemTrackingInterfaces.js";
8+
import path from "path";
89

910
const WORK_TOOLS = {
1011
list_team_iterations: "work_list_team_iterations",
@@ -113,25 +114,46 @@ function configureWorkTools(server: McpServer, _: () => Promise<string>, connect
113114
{
114115
project: z.string().describe("The name or ID of the Azure DevOps project."),
115116
depth: z.number().default(2).describe("Depth of children to fetch."),
117+
excludedIds: z.array(z.number()).optional().describe("An optional array of iteration IDs, and thier children, that should not be returned."),
116118
},
117-
async ({ project, depth }) => {
119+
async ({ project, depth, excludedIds: ids }) => {
118120
try {
119121
const connection = await connectionProvider();
120122
const workItemTrackingApi = await connection.getWorkItemTrackingApi();
123+
let results = [];
121124

122125
if (depth === undefined) {
123126
depth = 1;
124127
}
125128

126-
const results = await workItemTrackingApi.getClassificationNodes(project, [], depth);
129+
results = await workItemTrackingApi.getClassificationNodes(project, [], depth);
127130

128131
// Handle null or undefined results
129132
if (!results) {
130133
return { content: [{ type: "text", text: "No iterations were found" }], isError: true };
131134
}
132135

133136
// Filter out items with structureType=0 (Area nodes), only keep structureType=1 (Iteration nodes)
134-
const filteredResults = results.filter((node) => node.structureType === TreeNodeStructureType.Iteration);
137+
let filteredResults = results.filter((node) => node.structureType === TreeNodeStructureType.Iteration);
138+
139+
// If specific IDs are provided, filter them out recursively (exclude matching nodes and their children)
140+
if (ids && ids.length > 0) {
141+
const filterOutIds = (nodes: WorkItemClassificationNode[]): WorkItemClassificationNode[] => {
142+
return nodes
143+
.filter((node) => !node.id || !ids.includes(node.id))
144+
.map((node) => {
145+
if (node.children && node.children.length > 0) {
146+
return {
147+
...node,
148+
children: filterOutIds(node.children),
149+
};
150+
}
151+
return node;
152+
});
153+
};
154+
155+
filteredResults = filterOutIds(filteredResults);
156+
}
135157

136158
if (filteredResults.length === 0) {
137159
return { content: [{ type: "text", text: "No iterations were found" }], isError: true };

0 commit comments

Comments
 (0)