wit_work_item_unlink
Remove specific or all links from an Azure DevOps work item. Specify link type (e.g., parent, child, related) and optional URL for precise removal, simplifying work item management.
Instructions
Remove one or many links from a single work item
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | The ID of the work item to remove the links from. | |
| project | Yes | The name or ID of the Azure DevOps project. | |
| type | No | Type of link to remove. Options include 'parent', 'child', 'duplicate', 'duplicate of', 'related', 'successor', 'predecessor', 'tested by', 'tests', 'affects', 'affected by', and 'artifact'. Defaults to 'related'. | related |
| url | No | Optional URL to match for the link to remove. If not provided, all links of the specified type will be removed. |
Implementation Reference
- src/tools/workitems.ts:813-874 (handler)The main handler function implementing the 'wit_work_item_unlink' tool. It retrieves the work item's relations, identifies matching links by type and optional URL, constructs a PATCH to remove them, and updates the work item.async ({ project, id, type, url }) => { try { const connection = await connectionProvider(); const workItemApi = await connection.getWorkItemTrackingApi(); const workItem = await workItemApi.getWorkItem(id, undefined, undefined, WorkItemExpand.Relations, project); const relations: WorkItemRelation[] = workItem.relations ?? []; const linkType = getLinkTypeFromName(type); let relationIndexes: number[] = []; if (url && url.trim().length > 0) { // If url is provided, find relations matching both rel type and url relationIndexes = relations.map((relation, idx) => (relation.url === url ? idx : -1)).filter((idx) => idx !== -1); } else { // If url is not provided, find all relations matching rel type relationIndexes = relations.map((relation, idx) => (relation.rel === linkType ? idx : -1)).filter((idx) => idx !== -1); } if (relationIndexes.length === 0) { return { content: [{ type: "text", text: `No matching relations found for link type '${type}'${url ? ` and URL '${url}'` : ""}.\n${JSON.stringify(relations, null, 2)}` }], isError: true, }; } // Get the relations that will be removed for logging const removedRelations = relationIndexes.map((idx) => relations[idx]); // Sort indexes in descending order to avoid index shifting when removing relationIndexes.sort((a, b) => b - a); const apiUpdates = relationIndexes.map((idx) => ({ op: "remove", path: `/relations/${idx}`, })); const updatedWorkItem = await workItemApi.updateWorkItem(null, apiUpdates, id, project); return { content: [ { type: "text", text: `Removed ${removedRelations.length} link(s) of type '${type}':\n` + JSON.stringify(removedRelations, null, 2) + `\n\nUpdated work item result:\n` + JSON.stringify(updatedWorkItem, null, 2), }, ], isError: false, }; } catch (error) { return { content: [ { type: "text", text: `Error unlinking work item: ${error instanceof Error ? error.message : "Unknown error occurred"}`, }, ], isError: true, }; }
- src/tools/workitems.ts:803-812 (schema)Zod schema defining the input parameters for the 'wit_work_item_unlink' tool: project (string), id (number), type (enum of link types, default 'related'), url (optional string).project: z.string().describe("The name or ID of the Azure DevOps project."), id: z.number().describe("The ID of the work item to remove the links from."), type: z .enum(["parent", "child", "duplicate", "duplicate of", "related", "successor", "predecessor", "tested by", "tests", "affects", "affected by", "artifact"]) .default("related") .describe( "Type of link to remove. Options include 'parent', 'child', 'duplicate', 'duplicate of', 'related', 'successor', 'predecessor', 'tested by', 'tests', 'affects', 'affected by', and 'artifact'. Defaults to 'related'." ), url: z.string().optional().describe("Optional URL to match for the link to remove. If not provided, all links of the specified type will be removed."), },
- src/tools/workitems.ts:799-875 (registration)Tool registration using server.tool(), associating the name WORKITEM_TOOLS.work_item_unlink ('wit_work_item_unlink') with its description, input schema, and handler function.server.tool( WORKITEM_TOOLS.work_item_unlink, "Remove one or many links from a single work item", { project: z.string().describe("The name or ID of the Azure DevOps project."), id: z.number().describe("The ID of the work item to remove the links from."), type: z .enum(["parent", "child", "duplicate", "duplicate of", "related", "successor", "predecessor", "tested by", "tests", "affects", "affected by", "artifact"]) .default("related") .describe( "Type of link to remove. Options include 'parent', 'child', 'duplicate', 'duplicate of', 'related', 'successor', 'predecessor', 'tested by', 'tests', 'affects', 'affected by', and 'artifact'. Defaults to 'related'." ), url: z.string().optional().describe("Optional URL to match for the link to remove. If not provided, all links of the specified type will be removed."), }, async ({ project, id, type, url }) => { try { const connection = await connectionProvider(); const workItemApi = await connection.getWorkItemTrackingApi(); const workItem = await workItemApi.getWorkItem(id, undefined, undefined, WorkItemExpand.Relations, project); const relations: WorkItemRelation[] = workItem.relations ?? []; const linkType = getLinkTypeFromName(type); let relationIndexes: number[] = []; if (url && url.trim().length > 0) { // If url is provided, find relations matching both rel type and url relationIndexes = relations.map((relation, idx) => (relation.url === url ? idx : -1)).filter((idx) => idx !== -1); } else { // If url is not provided, find all relations matching rel type relationIndexes = relations.map((relation, idx) => (relation.rel === linkType ? idx : -1)).filter((idx) => idx !== -1); } if (relationIndexes.length === 0) { return { content: [{ type: "text", text: `No matching relations found for link type '${type}'${url ? ` and URL '${url}'` : ""}.\n${JSON.stringify(relations, null, 2)}` }], isError: true, }; } // Get the relations that will be removed for logging const removedRelations = relationIndexes.map((idx) => relations[idx]); // Sort indexes in descending order to avoid index shifting when removing relationIndexes.sort((a, b) => b - a); const apiUpdates = relationIndexes.map((idx) => ({ op: "remove", path: `/relations/${idx}`, })); const updatedWorkItem = await workItemApi.updateWorkItem(null, apiUpdates, id, project); return { content: [ { type: "text", text: `Removed ${removedRelations.length} link(s) of type '${type}':\n` + JSON.stringify(removedRelations, null, 2) + `\n\nUpdated work item result:\n` + JSON.stringify(updatedWorkItem, null, 2), }, ], isError: false, }; } catch (error) { return { content: [ { type: "text", text: `Error unlinking work item: ${error instanceof Error ? error.message : "Unknown error occurred"}`, }, ], isError: true, }; } }
- src/tools/workitems.ts:34-63 (helper)Helper function to map human-readable link type names to Azure DevOps internal relation type strings, used by the unlink handler.function getLinkTypeFromName(name: string) { switch (name.toLowerCase()) { case "parent": return "System.LinkTypes.Hierarchy-Reverse"; case "child": return "System.LinkTypes.Hierarchy-Forward"; case "duplicate": return "System.LinkTypes.Duplicate-Forward"; case "duplicate of": return "System.LinkTypes.Duplicate-Reverse"; case "related": return "System.LinkTypes.Related"; case "successor": return "System.LinkTypes.Dependency-Forward"; case "predecessor": return "System.LinkTypes.Dependency-Reverse"; case "tested by": return "Microsoft.VSTS.Common.TestedBy-Forward"; case "tests": return "Microsoft.VSTS.Common.TestedBy-Reverse"; case "affects": return "Microsoft.VSTS.Common.Affects-Forward"; case "affected by": return "Microsoft.VSTS.Common.Affects-Reverse"; case "artifact": return "ArtifactLink"; default: throw new Error(`Unknown link type: ${name}`); } }
- src/tools/workitems.ts:12-32 (registration)Constant object defining tool names, including 'work_item_unlink': 'wit_work_item_unlink', used in tool registration.const WORKITEM_TOOLS = { my_work_items: "wit_my_work_items", list_backlogs: "wit_list_backlogs", list_backlog_work_items: "wit_list_backlog_work_items", get_work_item: "wit_get_work_item", get_work_items_batch_by_ids: "wit_get_work_items_batch_by_ids", update_work_item: "wit_update_work_item", create_work_item: "wit_create_work_item", list_work_item_comments: "wit_list_work_item_comments", get_work_items_for_iteration: "wit_get_work_items_for_iteration", add_work_item_comment: "wit_add_work_item_comment", add_child_work_items: "wit_add_child_work_items", link_work_item_to_pull_request: "wit_link_work_item_to_pull_request", get_work_item_type: "wit_get_work_item_type", get_query: "wit_get_query", get_query_results_by_id: "wit_get_query_results_by_id", update_work_items_batch: "wit_update_work_items_batch", work_items_link: "wit_work_items_link", work_item_unlink: "wit_work_item_unlink", add_artifact_link: "wit_add_artifact_link", };