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}` }]
                };
            }
        }
    )
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden. It successfully discloses the 'atomic operation' nature of project association and the upsert behavior, but fails to clarify safety aspects (destructive potential of updates) or resolve the confusion around projectID's required status.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three tightly written sentences with zero waste. Information is front-loaded with the primary purpose, followed immediately by conditional logic for the two main parameters (releaseDbid and projectID).

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

While the description adequately covers the dual operation modes, the discrepancy regarding projectID's requirement status (description implies optional, schema requires) creates a significant gap. Without an output schema, the description also omits what the tool returns on success/failure.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, establishing a baseline of 3. The description adds valuable semantics for releaseDbid (explaining it acts as a mode toggle between create/update) but subtracts clarity for projectID by implying it is optional when the schema requires it, resulting in a neutral net score.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the dual purpose with specific verbs ('Creates... or updates') and resource ('release in Plan'), distinguishing it from siblings like create_or_update_sprint by explicitly naming the 'release' resource.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description clearly explains the conditional logic for create vs update modes based on releaseDbid presence. However, it misleadingly implies projectID is optional ('If projectID is provided') when the schema marks it as required, and does not mention when to use this tool versus alternatives like get_releases.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

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

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