update_work_item
Modify existing work item fields like description, owner, sprint, or release. Ensure PlannedRelease is set before assigning Sprint to maintain proper hierarchy.
Instructions
Updates fields of an existing work item. Provide the fields you want to update with their new values. IMPORTANT: When assigning a Sprint to a work item, the work item MUST first have the corresponding Release assigned to its PlannedRelease field. You cannot assign a sprint to a work item unless that work item is already part of the release that owns the sprint. Always update PlannedRelease before updating Sprint field.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| dbid | Yes | The dbid field from the workitem to identify it | |
| application | Yes | Name of the application | |
| fields | Yes | Array of fields to update. CRITICAL: To assign a Sprint, first ensure PlannedRelease is set to the release that contains that sprint, otherwise the Sprint assignment will fail. |
Implementation Reference
- src/lib/server.js:1403-1527 (handler)The implementation of the `update_work_item` tool. It uses a multi-step PATCH sequence (Modify action, Edit operation, Commit operation) to update fields of an existing WorkItem entity in Plan.
server.tool( "update_work_item", "Updates fields of an existing work item. Provide the fields you want to update with their new values. IMPORTANT: When assigning a Sprint to a work item, the work item MUST first have the corresponding Release assigned to its PlannedRelease field. You cannot assign a sprint to a work item unless that work item is already part of the release that owns the sprint. Always update PlannedRelease before updating Sprint field.", { dbid: z.string().describe("The dbid field from the workitem to identify it"), application: z.string().describe("Name of the application"), fields: z.array(z.object({ name: z.string().describe("Field name (e.g., 'Description', 'Owner', 'Component', 'Sprint', 'PlannedRelease', 'StoryPoints', 'BusinessValue', etc.)"), value: z.string().describe("The new value for the field. For 'Sprint' field, use the exact sprint name (e.g., 'Sprint 1 - Planning & Foundation'). For 'PlannedRelease' field, use the exact release name (e.g., 'Release 2')."), type: z.string().optional().describe("Field type (e.g., 'SHORT_STRING', 'MULTILINE_STRING', 'INT', 'REFERENCE', 'DATE_TIME'). Use 'REFERENCE' for Sprint and PlannedRelease fields. Defaults to 'SHORT_STRING'."), })).describe("Array of fields to update. CRITICAL: To assign a Sprint, first ensure PlannedRelease is set to the release that contains that sprint, otherwise the Sprint assignment will fail."), }, async ({ dbid, application, fields }) => { try { if (!globalCookies) { globalCookies = await getCookiesFromServer(serverURL); if (!globalCookies) { console.error("Failed to retrieve cookies from server."); return { error: "Failed to retrieve cookies." }; } console.log("Received Cookies:", globalCookies); } else { console.log("Reusing Stored Cookies:", globalCookies); } // Step 1: Modify action + Edit operation with empty body (like UI does) const modifyUrl = `${serverURL}/ccmweb/rest/repos/${teamspaceID}/databases/${application}/records/WorkItem/${dbid}?actionName=Modify&operation=Edit&useDbid=true`; const modifyResponse = await fetch(modifyUrl, { method: 'PATCH', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'Authorization': `Basic ${personal_access_token_string}`, 'Cookie': globalCookies }, body: "{}" , ...getAgentOptions(serverURL) }); if (!modifyResponse.ok) { const errorText = await modifyResponse.text(); throw new Error(`Modify action failed: ${modifyResponse.statusText} - ${errorText}`); } console.log("Modify action successful"); // Step 2: Edit operation with simple field structure (name and value only) const editBody = { fields: fields.map(field => ({ name: field.name, value: field.value })) }; const editUrl = `${serverURL}/ccmweb/rest/repos/${teamspaceID}/databases/${application}/records/WorkItem/${dbid}?operation=Edit&useDbid=true`; const editResponse = await fetch(editUrl, { method: 'PATCH', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'Authorization': `Basic ${personal_access_token_string}`, 'Cookie': globalCookies }, body: JSON.stringify(editBody) , ...getAgentOptions(serverURL) }); if (!editResponse.ok) { const errorText = await editResponse.text(); throw new Error(`Edit operation failed: ${editResponse.statusText} - ${errorText}`); } const editData = await editResponse.json(); console.log("Edit operation successful"); // Step 3: Commit operation with full field structure const commitBody = { dbId: dbid, fields: fields.map(field => ({ name: field.name, value: field.value, valueStatus: field.value ? "HAS_VALUE" : "HAS_NO_VALUE", validationStatus: "_KNOWN_VALID", requiredness: "OPTIONAL", requirednessForUser: "OPTIONAL", type: field.type || "SHORT_STRING", valueAsList: field.value ? [field.value] : [], messageText: "", maxLength: field.type === "SHORT_STRING" ? 254 : 0 })) }; const commitUrl = `${serverURL}/ccmweb/rest/repos/${teamspaceID}/databases/${application}/records/WorkItem/${dbid}?operation=Commit&useDbid=true`; const commitResponse = await fetch(commitUrl, { method: 'PATCH', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'Authorization': `Basic ${personal_access_token_string}`, 'Cookie': globalCookies }, body: JSON.stringify(commitBody) , ...getAgentOptions(serverURL) }); if (commitResponse.ok) { const result = await commitResponse.json(); const updatedFields = fields.map(f => `- ${f.name}: ${f.value}`).join('\n'); return { content: [{ type: 'text', text: `Work item ${dbid} updated successfully.\n\nUpdated fields:\n${updatedFields}` }] }; } else { const errorText = await commitResponse.text(); throw new Error(`Commit operation failed: ${commitResponse.statusText} - ${errorText}`); } } catch (e) { return { content: [{ type: 'text', text: `Error updating work item: ${e.message}` }] }; } } );