Skip to main content
Glama
mrchris2000

MCP DevOps Plan Server

by mrchris2000

create_or_update_release

Create or modify releases in DevOps Plan systems, adding them to projects automatically when specified.

Instructions

Creates a new release or updates an existing release in Plan. If releaseDbid is provided, updates the release; otherwise creates a new one. If projectID is provided, automatically adds the release to the project (atomic operation).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
applicationYesName of the application
projectIDYesThe ID of the project to automatically add this release to
releaseDbidNoThe dbid of the release to update (optional - omit to create new release)
fieldsYesArray of fields to set/update. For creation, 'Name' is required. When setting Sprints, always use type='REFERENCE_LIST' and provide sprint names, not dbids.

Implementation Reference

  • The 'create_or_update_release' tool handler definition and implementation. It handles creating a new release or updating an existing one, and optionally adds it to a project.
    // Tool to create or update a release
    server.tool(
        "create_or_update_release",
        "Creates a new release or updates an existing release in Plan. If releaseDbid is provided, updates the release; otherwise creates a new one. If projectID is provided, automatically adds the release to the project (atomic operation).",
        {
            application: z.string().describe("Name of the application"),
            projectID: z.string().describe("The ID of the project to automatically add this release to"),
            releaseDbid: z.string().optional().describe("The dbid of the release to update (optional - omit to create new release)"),
            fields: z.array(z.object({
                name: z.string().describe("Field name (e.g., 'Name', 'ReleaseType', 'Description', 'Frozen', 'Sprints', etc.)"),
                value: z.string().describe("The new value for the field. IMPORTANT: For REFERENCE_LIST fields like 'Sprints', use NAMES not IDs - provide comma-separated sprint names (e.g., 'Sprint 1,Sprint 2,Sprint 3')."),
                type: z.string().optional().describe("Field type (e.g., 'SHORT_STRING', 'MULTILINE_STRING', 'REFERENCE_LIST', 'DATE_TIME'). Use 'REFERENCE_LIST' for fields like 'Sprints' that reference other entities. Defaults to 'SHORT_STRING'.")
            })).describe("Array of fields to set/update. For creation, 'Name' is required. When setting Sprints, always use type='REFERENCE_LIST' and provide sprint names, not dbids.")
        },
        async ({ application, releaseDbid, projectID, 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);
                }
    
                let targetDbid = releaseDbid;
                const isCreating = !releaseDbid;
    
                // CREATE MODE: Step 1 - POST to create empty Release
                if (isCreating) {
                    const hasName = fields.some(f => f.name === "Name");
                    if (!hasName) {
                        throw new Error("Name field is required when creating a new release");
                    }
    
                    const createResponse = await fetch(`${serverURL}/ccmweb/rest/repos/${teamspaceID}/databases/${application}/records/Release?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 Release with dbId:", targetDbid);
                } else {
                    if (fields.length === 0) {
                        throw new Error("At least one field must be provided for update");
                    }
                }
    
                // Step 2: PATCH Edit to set fields (may need multiple calls)
                for (const field of fields) {
                    // For REFERENCE_LIST fields, we need to send valueAsList instead of value
                    const fieldPayload = { name: field.name };
                    if (field.type === "REFERENCE_LIST") {
                        fieldPayload.valueAsList = field.value.split(',').map(v => v.trim());
                    } else {
                        fieldPayload.value = field.value;
                    }
                    
                    const editPayload = { fields: [fieldPayload] };
    
                    const editResponse = await fetch(`${serverURL}/ccmweb/rest/repos/${teamspaceID}/databases/${application}/records/Release/${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 for ${field.name}: ${editResponse.status} ${errorText}`);
                    }
    
                    const editData = await editResponse.json();
                    //console.log(`Edit response for ${field.name}:`, JSON.stringify(editData));
                }
    
                // Step 3: PATCH Commit with full field metadata
                const commitFields = fields.map(field => {
                    const baseField = {
                        name: field.name,
                        valueStatus: "HAS_VALUE",
                        validationStatus: "_KNOWN_VALID",
                        requiredness: field.name === "Name" ? "MANDATORY" : "OPTIONAL",
                        requirednessForUser: field.name === "Name" ? "MANDATORY" : "OPTIONAL",
                        type: field.type || "SHORT_STRING",
                        messageText: "",
                        maxLength: (field.type === "MULTILINE_STRING" || field.type === "REFERENCE_LIST") ? 0 : 254
                    };
    
                    // Handle REFERENCE_LIST type (like Sprints)
                    if (field.type === "REFERENCE_LIST") {
                        baseField.valueAsList = field.value.split(',').map(v => v.trim());
                        // For REFERENCE_LIST, join values with newline for the value field
                        baseField.value = baseField.valueAsList.join('\n');
                    } else {
                        baseField.value = field.value;
                        baseField.valueAsList = [field.value];
                    }
    
                    return baseField;
                });
    
                const commitPayload = {
                    dbId: targetDbid,
                    fields: commitFields
                };
    
                const commitResponse = await fetch(`${serverURL}/ccmweb/rest/repos/${teamspaceID}/databases/${application}/records/Release/${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();
                
                // Get the release name for adding to project
                const releaseName = fields.find(f => f.name === "Name")?.value;
    
                // Step 4: If projectID is provided, add this release to the project
                if (projectID && releaseName) {
                    try {
                        // First, get the current project to retrieve existing releases
                        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 releasesField = projectData.fields.find(f => f.name === "Releases");
                            
                            // Get existing releases and add the new one if not already present
                            let existingReleases = releasesField?.valueAsList || [];
                            if (!existingReleases.includes(releaseName)) {
                                existingReleases.push(releaseName);
                                
                                // Update the project with the new releases list
                                const projectCommitPayload = {
                                    dbId: projectID,
                                    fields: [{
                                        name: "Releases",
                                        value: existingReleases.join('\n'),
                                        valueStatus: "HAS_VALUE",
                                        validationStatus: "_KNOWN_VALID",
                                        requiredness: "OPTIONAL",
                                        requirednessForUser: "OPTIONAL",
                                        type: "REFERENCE_LIST",
                                        valueAsList: existingReleases,
                                        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 release ${releaseName} to project ${projectID}`);
                                } else {
                                    const errorText = await updateProjectResponse.text();
                                    console.warn(`Failed to add release to project: ${errorText}`);
                                }
                            } else {
                                console.log(`Release ${releaseName} already in project ${projectID}`);
                            }
                        }
                    } catch (projectError) {
                        console.warn(`Failed to update project with release: ${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: `Release ${action} successfully${projectMessage}: ${JSON.stringify(commitData)}` }]
                };
            } catch (e) {
                return {
                    content: [{ type: 'text', text: `Error ${releaseDbid ? 'updating' : 'creating'} release: ${e.message}` }]
                };
            }
        }
    )

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/mrchris2000/mcp-devops-plan'

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