Skip to content

Commit 7437859

Browse files
authored
Added optional fields param to get work items in batch (#385)
Added new optional `fields` param when getting work items by id in batch ## GitHub issue number #383 ## **Associated Risks** N/A ## ✅ **PR Checklist** - [x] **I have read the [contribution guidelines](https://github.com/microsoft/azure-devops-mcp/blob/main/CONTRIBUTING.md)** - [x] **I have read the [code of conduct guidelines](https://github.com/microsoft/azure-devops-mcp/blob/main/CODE_OF_CONDUCT.md)** - [x] Title of the pull request is clear and informative. - [x] 👌 Code hygiene - [x] 🔭 Telemetry added, updated, or N/A - [x] 📄 Documentation added, updated, or N/A - [x] 🛡️ Automated tests added, or N/A ## 🧪 **How did you test it?** Updated manual tests. manual and copilot testing
1 parent 4345602 commit 7437859

File tree

2 files changed

+76
-3
lines changed

2 files changed

+76
-3
lines changed

src/tools/workitems.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,12 +129,17 @@ function configureWorkItemTools(server: McpServer, tokenProvider: () => Promise<
129129
{
130130
project: z.string().describe("The name or ID of the Azure DevOps project."),
131131
ids: z.array(z.number()).describe("The IDs of the work items to retrieve."),
132+
fields: z.array(z.string()).optional().describe("Optional list of fields to include in the response. If not provided, a hardcoded default set of fields will be used."),
132133
},
133-
async ({ project, ids }) => {
134+
async ({ project, ids, fields }) => {
134135
const connection = await connectionProvider();
135136
const workItemApi = await connection.getWorkItemTrackingApi();
136-
const fields = ["System.Id", "System.WorkItemType", "System.Title", "System.State", "System.Parent", "System.Tags", "Microsoft.VSTS.Common.StackRank", "System.AssignedTo"];
137-
const workitems = await workItemApi.getWorkItemsBatch({ ids, fields }, project);
137+
const defaultFields = ["System.Id", "System.WorkItemType", "System.Title", "System.State", "System.Parent", "System.Tags", "Microsoft.VSTS.Common.StackRank", "System.AssignedTo"];
138+
139+
// If no fields are provided, use the default set of fields
140+
const fieldsToUse = !fields || fields.length === 0 ? defaultFields : fields;
141+
142+
const workitems = await workItemApi.getWorkItemsBatch({ ids, fields: fieldsToUse }, project);
138143

139144
// Format the assignedTo field to include displayName and uniqueName
140145
// Removing the identity object as the response. It's too much and not needed

test/src/tools/workitems.test.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,74 @@ describe("configureWorkItemTools", () => {
293293
expect(result.content[0].text).toBe(JSON.stringify([_mockWorkItems], null, 2));
294294
});
295295

296+
it("should call workItemApi.getWorkItemsBatch API with custom fields when fields parameter is provided", async () => {
297+
configureWorkItemTools(server, tokenProvider, connectionProvider, userAgentProvider);
298+
299+
const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "wit_get_work_items_batch_by_ids");
300+
301+
if (!call) throw new Error("wit_get_work_items_batch_by_ids tool not registered");
302+
const [, , , handler] = call;
303+
304+
const mockWorkItemsWithCustomFields = [
305+
{
306+
id: 297,
307+
fields: {
308+
"System.Id": 297,
309+
"System.Title": "Test Work Item",
310+
},
311+
},
312+
];
313+
314+
(mockWorkItemTrackingApi.getWorkItemsBatch as jest.Mock).mockResolvedValue(mockWorkItemsWithCustomFields);
315+
316+
const params = {
317+
ids: [297, 299, 300],
318+
project: "Contoso",
319+
fields: ["System.Id", "System.Title"],
320+
};
321+
322+
const result = await handler(params);
323+
324+
expect(mockWorkItemTrackingApi.getWorkItemsBatch).toHaveBeenCalledWith(
325+
{
326+
ids: params.ids,
327+
fields: params.fields,
328+
},
329+
params.project
330+
);
331+
332+
expect(result.content[0].text).toBe(JSON.stringify(mockWorkItemsWithCustomFields, null, 2));
333+
});
334+
335+
it("should use default fields when an empty fields array is provided", async () => {
336+
configureWorkItemTools(server, tokenProvider, connectionProvider, userAgentProvider);
337+
338+
const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "wit_get_work_items_batch_by_ids");
339+
340+
if (!call) throw new Error("wit_get_work_items_batch_by_ids tool not registered");
341+
const [, , , handler] = call;
342+
343+
(mockWorkItemTrackingApi.getWorkItemsBatch as jest.Mock).mockResolvedValue([_mockWorkItems]);
344+
345+
const params = {
346+
ids: [297, 299, 300],
347+
project: "Contoso",
348+
fields: [], // Empty array should trigger default fields
349+
};
350+
351+
const result = await handler(params);
352+
353+
expect(mockWorkItemTrackingApi.getWorkItemsBatch).toHaveBeenCalledWith(
354+
{
355+
ids: params.ids,
356+
fields: ["System.Id", "System.WorkItemType", "System.Title", "System.State", "System.Parent", "System.Tags", "Microsoft.VSTS.Common.StackRank", "System.AssignedTo"],
357+
},
358+
params.project
359+
);
360+
361+
expect(result.content[0].text).toBe(JSON.stringify([_mockWorkItems], null, 2));
362+
});
363+
296364
it("should transform System.AssignedTo object to formatted string", async () => {
297365
configureWorkItemTools(server, tokenProvider, connectionProvider, userAgentProvider);
298366

0 commit comments

Comments
 (0)