Skip to main content
Glama
ennuiii

Azure DevOps MCP Server with PAT Authentication

by ennuiii

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
NameRequiredDescriptionDefault
idYesThe ID of the work item to remove the links from.
projectYesThe name or ID of the Azure DevOps project.
typeNoType 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
urlNoOptional URL to match for the link to remove. If not provided, all links of the specified type will be removed.

Implementation Reference

  • 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, }; }
  • 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."), },
  • 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, }; } }
  • 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}`); } }
  • 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", };

Other Tools

Related Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ennuiii/DevOpsMcpPAT'

If you have feedback or need assistance with the MCP directory API, please join our Discord server