|
4 | 4 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; |
5 | 5 | import { WebApi } from "azure-devops-node-api"; |
6 | 6 | 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"; |
8 | 9 |
|
9 | 10 | const WORK_TOOLS = { |
10 | 11 | list_team_iterations: "work_list_team_iterations", |
@@ -113,25 +114,46 @@ function configureWorkTools(server: McpServer, _: () => Promise<string>, connect |
113 | 114 | { |
114 | 115 | project: z.string().describe("The name or ID of the Azure DevOps project."), |
115 | 116 | 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."), |
116 | 118 | }, |
117 | | - async ({ project, depth }) => { |
| 119 | + async ({ project, depth, excludedIds: ids }) => { |
118 | 120 | try { |
119 | 121 | const connection = await connectionProvider(); |
120 | 122 | const workItemTrackingApi = await connection.getWorkItemTrackingApi(); |
| 123 | + let results = []; |
121 | 124 |
|
122 | 125 | if (depth === undefined) { |
123 | 126 | depth = 1; |
124 | 127 | } |
125 | 128 |
|
126 | | - const results = await workItemTrackingApi.getClassificationNodes(project, [], depth); |
| 129 | + results = await workItemTrackingApi.getClassificationNodes(project, [], depth); |
127 | 130 |
|
128 | 131 | // Handle null or undefined results |
129 | 132 | if (!results) { |
130 | 133 | return { content: [{ type: "text", text: "No iterations were found" }], isError: true }; |
131 | 134 | } |
132 | 135 |
|
133 | 136 | // 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 | + } |
135 | 157 |
|
136 | 158 | if (filteredResults.length === 0) { |
137 | 159 | return { content: [{ type: "text", text: "No iterations were found" }], isError: true }; |
|
0 commit comments