Skip to main content
Glama
zalab-inc
by zalab-inc

update_issue

Modify Linear issue details including title, description, status, priority, due date, and parent relationships to keep project tracking current.

Instructions

A tool that updates an issue in Linear

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
descriptionNoThe description of the issue
dueDateNoThe due date of the issue
idYesThe ID of the issue to update
parentIdNoThe ID of the parent issue, used to create a sub-issue
priorityNoThe priority of the issueno_priority
sortOrderNoThe sort order of the issue
statusNoThe status of the issuebacklog
titleNoThe title of the issue
trashedNoWhether the issue is trashed

Implementation Reference

  • The handler function that executes the tool logic for updating a Linear issue. It validates inputs, resolves state IDs, calls the Linear API to update the issue, handles responses, and formats output.
    handler: async (args: z.infer<typeof updateIssueSchema>) => {
      try {
        // Validate input
        if (!args.id || args.id.trim() === "") {
          return {
            content: [{
              type: "text",
              text: "Error: Issue ID cannot be empty",
            }],
          };
        }
        
        // Convert priority from string to number if provided
        let priorityValue: number | undefined;
        if (args.priority) {
          priorityValue = PriorityStringToNumber[args.priority];
          if (priorityValue === undefined) {
            return {
              content: [{
                type: "text",
                text: "Error: Priority must be a valid string (no_priority, urgent, high, medium, low)",
              }],
            };
          }
        }
        
        // Get the issue to update to retrieve its team ID
        let teamId: string | undefined;
        try {
          const issueResponse = await linearClient.issue(args.id);
          if (issueResponse) {
            const issueData = await issueResponse;
            const team = await issueData.team;
            if (team) {
              teamId = team.id;
            }
          }
        } catch (error) {
          console.error("Error fetching issue for team ID:", error);
        }
        
        // Get valid state ID from Linear API if status is provided
        let stateId: string | undefined;
        if (args.status && teamId) {
          // Normalize the state name to handle different variations
          const normalizedStateName = normalizeStateName(args.status);
          // Get the actual state ID from Linear API
          stateId = await getStateId(normalizedStateName, teamId, linearClient);
          
          if (!stateId) {
            return {
              content: [{
                type: "text",
                text: `Error: Could not find a valid state ID for "${args.status}" in team of issue ${args.id}`,
              }],
            };
          }
        }
        
        // Update the issue
        const updateIssueResponse = await linearClient.updateIssue(args.id, {
          title: args.title,
          description: args.description,
          trashed: args.trashed,
          dueDate: args.dueDate,
          sortOrder: args.sortOrder,
          stateId: stateId,
          priority: priorityValue,
          parentId: args.parentId,
        });
        
        if (!updateIssueResponse) {
          return {
            content: [{
              type: "text",
              text: "Failed to update issue. Please check your parameters and try again.",
            }],
          };
        }
        
        // Get issue ID from response
        // Linear SDK returns results in success and entity pattern
        if (updateIssueResponse.success) {
          // Access issue and get ID with the correct data type
          const issue = await updateIssueResponse.issue;
          if (issue && issue.id) {
            return {
              content: [{
                type: "text",
                text: `Status: Success\nMessage: Linear issue updated\nIssue ID: ${issue.id}`,
              }],
            };
          }
        }
        
        // Extract data from response - fix to handle proper response structure
        const updateResponse = updateIssueResponse as unknown as LinearUpdateResponse;
        
        // Check if the response follows the expected structure with success flag
        if (updateResponse.success === false) {
          return {
            content: [{
              type: "text",
              text: "Failed to update issue. Please check your parameters and try again.",
            }],
          };
        }
        
        // Extract issue data from the correct property
        const issueData: IssueResponseData = updateResponse.issue || updateIssueResponse as unknown as IssueResponseData;
        
        // Directly check the parsed response result
        const issueId = issueData?.id || (updateIssueResponse as unknown as { id?: string })?.id;
        if (issueId) {
          return {
            content: [{
              type: "text",
              text: `Status: Success\nMessage: Linear issue updated\nIssue ID: ${issueId}`,
            }],
          };
        }
        
        if (!issueData) {
          // Display success message even if data is incomplete
          return {
            content: [{
              type: "text",
              text: "Status: Success\nMessage: Linear issue updated",
            }],
          };
        }
        
        if (!issueData.id) {
          // Issue data exists but no ID
          return {
            content: [{
              type: "text",
              text: "Status: Success\nMessage: Linear issue updated (ID not available)",
            }],
          };
        }
        
        // Success case with ID available
        if (issueData.title === undefined && issueData.description === undefined) {
          // Only ID is available, without complete data
          return {
            content: [{
              type: "text",
              text: `Status: Success\nMessage: Linear issue updated\nIssue ID: ${issueData.id}`,
            }],
          };
        }
        
        // Format issue data to human-readable text
        const formattedText = formatIssueToHumanReadable(issueData);
        
        // Return formatted text
        return {
          content: [{
            type: "text",
            text: formattedText,
          }],
        };
      } catch (error) {
        // Handle errors gracefully
        const errorMessage = error instanceof Error ? error.message : "Unknown error";
        return {
          content: [{
            type: "text",
            text: `An error occurred while updating the issue:\n${errorMessage}`,
          }],
        };
      }
    },
  • Zod schema defining the input parameters and validation for the update_issue tool.
    const updateIssueSchema = z.object({
      id: z.string().describe("The ID of the issue to update"),
      title: z.string().describe("The title of the issue").optional(),
      description: z.string().describe("The description of the issue").optional(),
      dueDate: z.string().describe("The due date of the issue").optional(),
      status: z.enum([
        "triage", "backlog", "todo", "in_progress", "done", "canceled"
      ]).default("backlog").describe("The status of the issue"),
      priority: z.enum([
        "no_priority", "urgent", "high", "medium", "low"
      ]).default("no_priority").describe("The priority of the issue"),
      sortOrder: z.number().describe("The sort order of the issue").optional(),
      trashed: z.boolean().describe("Whether the issue is trashed").optional(),
      parentId: z.string().describe("The ID of the parent issue, used to create a sub-issue").optional(),
    });
  • src/index.ts:31-41 (registration)
    Registration of the LinearUpdateIssueTool (internal name 'update_issue') along with other tools to the MCP server.
    registerTool(server, [
      LinearSearchIssuesTool,
      LinearGetProfileTool,
      LinearCreateIssueTool,
      LinearCreateCommentTool,
      LinearUpdateCommentTool,
      LinearGetIssueTool,
      LinearGetTeamIdTool,
      LinearUpdateIssueTool,
      LinearGetCommentTool,
    ]);
  • Helper function used by the handler to format the updated issue data into a detailed human-readable string.
    function formatIssueToHumanReadable(issue: IssueResponseData): string {
      if (!issue || !issue.id) {
        return "Invalid or incomplete issue data";
      }
    
      let result = "LINEAR ISSUE UPDATED\n";
      result += "==================\n\n";
      
      // Basic information
      result += `--- ISSUE DETAILS ---\n`;
      result += `ID: ${issue.id}\n`;
      result += `TITLE: ${safeText(issue.title)}\n`;
      result += `DESCRIPTION: ${safeText(issue.description)}\n\n`;
      
      // Status and priority
      result += `--- STATUS INFO ---\n`;
      if (issue.state && issue.state.name) {
        result += `STATUS: ${issue.state.name}\n`;
      }
      result += `PRIORITY: ${getPriorityLabel(issue.priority)}\n\n`;
      
      // Parent information if exists
      if (issue.parent && issue.parent.id) {
        result += `--- PARENT ISSUE ---\n`;
        result += `PARENT ID: ${issue.parent.id}\n`;
        if (issue.parent.title) {
          result += `PARENT TITLE: ${safeText(issue.parent.title)}\n`;
        }
        result += `\n`;
      }
      
      // Team information
      result += `--- TEAM INFO ---\n`;
      if (issue.team && issue.team.name) {
        result += `TEAM: ${issue.team.name}\n`;
      }
      
      // Assignee information
      if (issue.assignee && issue.assignee.name) {
        result += `ASSIGNEE: ${issue.assignee.name}\n`;
      }
      
      // Dates
      result += `--- TIME INFO ---\n`;
      if (issue.createdAt) {
        result += `CREATED AT: ${formatDate(issue.createdAt)}\n`;
      }
      result += `UPDATED AT: ${formatDate(issue.updatedAt)}\n`;
      
      // Due date if present
      if (issue.dueDate) {
        result += `DUE DATE: ${formatDate(issue.dueDate)}\n`;
      }
      
      // URL
      result += `\n--- ACCESS INFO ---\n`;
      result += `URL: ${safeText(issue.url)}\n\n`;
      
      result += "The issue has been successfully updated in Linear.";
      
      return result;
    }
  • Re-export of LinearUpdateIssueTool from its implementation file, facilitating import in src/index.ts.
    export { 
      LinearGetProfileTool, 
      LinearSearchIssuesTool,
      LinearCreateIssueTool,
      LinearUpdateIssueTool,
      LinearCreateCommentTool,
      LinearUpdateCommentTool,
      LinearGetIssueTool,
      LinearGetTeamIdTool,
      LinearGetCommentTool
Behavior2/5

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

No annotations are provided, so the description carries the full burden of behavioral disclosure. It states the tool 'updates an issue' which implies a mutation operation, but it doesn't disclose any behavioral traits such as required permissions, whether updates are reversible, rate limits, or what happens on success/failure. For a mutation tool with zero annotation coverage, this is a significant gap in transparency.

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?

The description is a single, efficient sentence that directly states the tool's purpose with zero waste. It is appropriately sized and front-loaded, making it easy for an agent to parse quickly. Every word earns its place, and there's no unnecessary elaboration.

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

Completeness2/5

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

Given the complexity (a mutation tool with 9 parameters, no annotations, and no output schema), the description is incomplete. It doesn't address behavioral aspects like permissions or side effects, provide usage context, or explain return values. For a tool that modifies data in a system like Linear, this leaves significant gaps that could hinder correct agent invocation.

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?

The schema description coverage is 100%, meaning all parameters are documented in the input schema. The description adds no additional meaning beyond what the schema provides (e.g., it doesn't explain parameter interactions or provide examples). With high schema coverage, the baseline is 3, as the description doesn't compensate but also doesn't detract from the schema's documentation.

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

Purpose4/5

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

The description clearly states the tool's purpose as 'updates an issue in Linear' with a specific verb ('updates') and resource ('issue in Linear'). It distinguishes from siblings like 'create_issue' (creation vs. update) and 'search_issues' (search vs. update), though it doesn't explicitly mention all siblings. The purpose is clear but could be more specific about what aspects can be updated.

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

Usage Guidelines2/5

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

The description provides no guidance on when to use this tool versus alternatives. It doesn't mention prerequisites (e.g., needing an existing issue ID), when to choose 'update_issue' over 'create_issue' or 'update_comment', or any constraints like permissions. Usage is implied by the name but not explicitly stated, leaving the agent to infer from context.

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/zalab-inc/mcp-linear-app'

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