create_or_update_sprint
Create new sprints or update existing ones in DevOps Plan systems, with optional automatic project assignment for streamlined sprint management.
Instructions
Creates a new sprint or updates an existing sprint in Plan. If sprintDbid is provided, updates the sprint; otherwise creates a new one. If projectID is provided, automatically adds the sprint to the project (atomic operation).
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| application | Yes | Name of the application | |
| sprintDbid | No | The dbid of the sprint to update (optional - omit to create new sprint) | |
| projectID | No | The dbid of the project to automatically add this sprint to (optional but recommended) | |
| name | No | Name of the sprint (required for creation, optional for update) | |
| startDate | No | Start date in YYYY-MM-DD format (optional) | |
| endDate | No | End date in YYYY-MM-DD format (optional) |
Implementation Reference
- src/lib/server.js:554-796 (handler)The handler function `create_or_update_sprint` implements the logic to create a new sprint or update an existing one in the Plan system. It uses an Edit+Commit pattern to manage the entity's lifecycle and optionally links it to a project.
// Tool to create or update a sprint server.tool( "create_or_update_sprint", "Creates a new sprint or updates an existing sprint in Plan. If sprintDbid is provided, updates the sprint; otherwise creates a new one. If projectID is provided, automatically adds the sprint to the project (atomic operation).", { application: z.string().describe("Name of the application"), sprintDbid: z.string().optional().describe("The dbid of the sprint to update (optional - omit to create new sprint)"), projectID: z.string().optional().describe("The dbid of the project to automatically add this sprint to (optional but recommended)"), name: z.string().optional().describe("Name of the sprint (required for creation, optional for update)"), startDate: z.string().optional().describe("Start date in YYYY-MM-DD format (optional)"), endDate: z.string().optional().describe("End date in YYYY-MM-DD format (optional)") }, async ({ application, sprintDbid, projectID, name, startDate, endDate }) => { 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); } let targetDbid = sprintDbid; const isCreating = !sprintDbid; // CREATE MODE: Step 1 - POST to create empty Sprint if (isCreating) { if (!name) { throw new Error("Name is required when creating a new sprint"); } const createResponse = await fetch(`${serverURL}/ccmweb/rest/repos/${teamspaceID}/databases/${application}/records/Sprint?operation=Edit&useDbid=true`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Basic ${personal_access_token_string}`, 'Cookie': globalCookies }, body: JSON.stringify({ fields: [] }) , ...getAgentOptions(serverURL) }); if (!createResponse.ok) { const errorText = await createResponse.text(); throw new Error(`Create operation failed: ${createResponse.status} ${errorText}`); } const createData = await createResponse.json(); targetDbid = createData.dbId; console.log("Created Sprint with dbId:", targetDbid); } else { // UPDATE MODE: Validate at least one field to update if (!name && !startDate && !endDate) { throw new Error("At least one of name, startDate, or endDate must be provided for update"); } } // Step 2: PATCH Edit to set fields const editFields = []; if (name) { editFields.push({ name: "Name", value: name }); } if (startDate) { editFields.push({ name: "StartDate", value: startDate }); } if (endDate) { editFields.push({ name: "EndDate", value: endDate }); } const editPayload = { fields: editFields }; const editResponse = await fetch(`${serverURL}/ccmweb/rest/repos/${teamspaceID}/databases/${application}/records/Sprint/${targetDbid}?operation=Edit&useDbid=true`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', 'Authorization': `Basic ${personal_access_token_string}`, 'Cookie': globalCookies }, body: JSON.stringify(editPayload) , ...getAgentOptions(serverURL) }); if (!editResponse.ok) { const errorText = await editResponse.text(); throw new Error(`Edit operation failed: ${editResponse.status} ${errorText}`); } const editData = await editResponse.json(); console.log("Edit response:", JSON.stringify(editData)); // Step 3: PATCH Commit with full field metadata const commitFields = []; if (name) { commitFields.push({ name: "Name", value: name, valueStatus: "HAS_VALUE", validationStatus: "_KNOWN_VALID", requiredness: "MANDATORY", requirednessForUser: "MANDATORY", type: "SHORT_STRING", valueAsList: [name], messageText: "", maxLength: 254 }); } if (startDate) { commitFields.push({ name: "StartDate", value: `${startDate} 00:00:00`, valueStatus: "HAS_VALUE", validationStatus: "_KNOWN_VALID", requiredness: "MANDATORY", requirednessForUser: "MANDATORY", type: "DATE_TIME", valueAsList: [`${startDate} 00:00:00`], messageText: "", maxLength: 0 }); } if (endDate) { commitFields.push({ name: "EndDate", value: `${endDate} 00:00:00`, valueStatus: "HAS_VALUE", validationStatus: "_KNOWN_VALID", requiredness: "MANDATORY", requirednessForUser: "MANDATORY", type: "DATE_TIME", valueAsList: [`${endDate} 00:00:00`], messageText: "", maxLength: 0 }); } const commitPayload = { dbId: targetDbid, fields: commitFields }; const commitResponse = await fetch(`${serverURL}/ccmweb/rest/repos/${teamspaceID}/databases/${application}/records/Sprint/${targetDbid}?operation=Commit&useDbid=true`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', 'Authorization': `Basic ${personal_access_token_string}`, 'Cookie': globalCookies }, body: JSON.stringify(commitPayload) , ...getAgentOptions(serverURL) }); if (!commitResponse.ok) { const errorText = await commitResponse.text(); throw new Error(`Commit operation failed: ${commitResponse.status} ${errorText}`); } const commitData = await commitResponse.json(); // Step 4: If projectID is provided, add this sprint to the project if (projectID && name) { try { // First, get the current project to retrieve existing sprints const getProjectResponse = await fetch(`${serverURL}/ccmweb/rest/repos/${teamspaceID}/databases/${application}/records/Project/${projectID}?useDbid=true`, { method: 'GET', headers: { 'Content-Type': 'application/json', 'Authorization': `Basic ${personal_access_token_string}`, 'Cookie': globalCookies } , ...getAgentOptions(serverURL) }); if (getProjectResponse.ok) { const projectData = await getProjectResponse.json(); const sprintsField = projectData.fields.find(f => f.name === "Sprints"); // Get existing sprints and add the new one if not already present let existingSprints = sprintsField?.valueAsList || []; if (!existingSprints.includes(name)) { existingSprints.push(name); // Update the project with the new sprints list const projectCommitPayload = { dbId: projectID, fields: [{ name: "Sprints", value: existingSprints.join('\n'), valueStatus: "HAS_VALUE", validationStatus: "_KNOWN_VALID", requiredness: "OPTIONAL", requirednessForUser: "OPTIONAL", type: "REFERENCE_LIST", valueAsList: existingSprints, messageText: "", maxLength: 0 }] }; const updateProjectResponse = await fetch(`${serverURL}/ccmweb/rest/repos/${teamspaceID}/databases/${application}/records/Project/${projectID}?operation=Commit&useDbid=true`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', 'Authorization': `Basic ${personal_access_token_string}`, 'Cookie': globalCookies }, body: JSON.stringify(projectCommitPayload) , ...getAgentOptions(serverURL) }); if (updateProjectResponse.ok) { console.log(`Added sprint ${name} to project ${projectID}`); } else { const errorText = await updateProjectResponse.text(); console.warn(`Failed to add sprint to project: ${errorText}`); } } else { console.log(`Sprint ${name} already in project ${projectID}`); } } } catch (projectError) { console.warn(`Failed to update project with sprint: ${projectError.message}`); // Don't fail the whole operation if project update fails } } const action = isCreating ? "created" : "updated"; const projectMessage = projectID ? ` and added to project ${projectID}` : ""; return { content: [{ type: 'text', text: `Sprint ${action} successfully${projectMessage}: ${JSON.stringify(commitData)}` }] }; } catch (e) { return { content: [{ type: 'text', text: `Error ${sprintDbid ? 'updating' : 'creating'} sprint: ${e.message}` }] }; } } )