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

get_issue

Retrieve specific issue details from Linear's project management system using the issue ID to access task information, status, and related data.

Instructions

A tool that gets an issue from Linear

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
issueIdYesThe ID of the issue to retrieve

Implementation Reference

  • Main handler for the get_issue tool. Fetches Linear issue by ID, retrieves comments, status, sub-issues, formats into human-readable text using createSafeTool wrapper.
    export const LinearGetIssueTool = createSafeTool({
      name: "get_issue",
      description: "A tool that gets an issue from Linear",
      schema: getIssueSchema.shape,
      handler: async (args: z.infer<typeof getIssueSchema>) => {
        try {
          // Validate input ID
          if (!args.issueId || args.issueId.trim() === "") {
            return {
              content: [{
                type: "text",
                text: "Error: Issue ID cannot be empty",
              }],
            };
          }
          
          // Fetch the issue using the Linear client
          const issue = await linearClient.issue(args.issueId);
          
          // Return an error message if the issue doesn't exist
          if (!issue) {
            return {
              content: [{
                type: "text",
                text: "Issue not found with that ID.",
              }],
            };
          }
          
          // Safely fetch comments with error handling
          let comments: IssueComment[] = [];
          try {
            const commentsQuery = await issue.comments();
            
            // Process and transform comments to a consistent format
            comments = commentsQuery.nodes.map((comment: unknown): IssueComment => {
              try {
                // Apply explicit type checking to ensure proper comment structure
                const typedComment = comment as {
                  id: string;
                  body: string;
                  createdAt: string;
                  updatedAt: string;
                  user: { id: string; name: string; email: string } | null;
                };
                
                // Extract and format relevant comment data
                return {
                  id: typedComment.id || "unknown-id",
                  body: typedComment.body || "",
                  createdAt: typedComment.createdAt || new Date().toISOString(),
                  updatedAt: typedComment.updatedAt || new Date().toISOString(),
                  user: typedComment.user ? {
                    id: typedComment.user.id || "unknown-user-id",
                    name: typedComment.user.name || "Unknown User",
                    email: typedComment.user.email || ""
                  } : null
                };
              } catch {
                // Return fallback comment on parsing error
                return {
                  id: "error-parsing",
                  body: "Error loading comment content",
                  createdAt: new Date().toISOString(),
                  updatedAt: new Date().toISOString(),
                  user: null
                };
              }
            });
          } catch {
            // Fail gracefully if comments can't be loaded
            comments = [{
              id: "comments-error",
              body: "Could not load comments: server error",
              createdAt: new Date().toISOString(),
              updatedAt: new Date().toISOString(),
              user: null
            }];
          }
    
          // Safely fetch issue state/status with error handling
          let status: StatusData | null = null;
          try {
            const stateData = await issue.state;
            if (stateData) {
              status = {
                id: stateData.id || "unknown-id",
                name: stateData.name || "Unknown status",
                color: stateData.color || "#cccccc",
                type: stateData.type || "unknown",
              };
            }
          } catch {
            // Fail gracefully if status can't be loaded
            status = {
              id: "status-error",
              name: "Error loading status",
              color: "#cccccc",
              type: "error",
            };
          }
          
          // Fetch sub-issues with error handling
          let subIssues: SubIssueData[] = [];
          try {
            // Get child issues using the Linear client
            const childIssuesQuery = await issue.children();
            
            if (childIssuesQuery && childIssuesQuery.nodes) {
              // Process each sub-issue
              for (const childIssue of childIssuesQuery.nodes) {
                try {
                  // Get status for each sub-issue
                  let subIssueStatus: StatusData | null = null;
                  try {
                    const childStateData = await childIssue.state;
                    if (childStateData) {
                      subIssueStatus = {
                        id: childStateData.id || "unknown-id",
                        name: childStateData.name || "Unknown status",
                        color: childStateData.color || "#cccccc",
                        type: childStateData.type || "unknown",
                      };
                    }
                  } catch {
                    subIssueStatus = null;
                  }
                  
                  // Add sub-issue to the array
                  subIssues.push({
                    id: childIssue.id || "unknown-id",
                    title: childIssue.title || "No title",
                    priority: typeof childIssue.priority === 'number' ? childIssue.priority : 0,
                    status: subIssueStatus
                  });
                } catch {
                  // Skip sub-issues that can't be processed
                  continue;
                }
              }
            }
          } catch {
            // Fail gracefully if sub-issues can't be loaded
            subIssues = [];
          }
          
          // Create normalized issue data object with safe defaults
          const issueData: IssueData = {
            id: issue.id || "unknown-id",
            title: issue.title || "No title",
            description: issue.description || undefined,
            priority: typeof issue.priority === 'number' ? issue.priority : 0,
            createdAt: issue.createdAt || new Date(),
            updatedAt: issue.updatedAt || new Date(),
            dueDate: issue.dueDate || undefined,
            url: issue.url || `https://linear.app/issue/${args.issueId}`,
            assignee: issue.assignee || undefined,
            labels: issue.labels || undefined
          };
          
          // Format issue data to human readable text
          const formattedText = formatIssueToHumanReadable(issueData, comments, status, subIssues);
          
          // Return the formatted text
          return {
            content: [{
              type: "text",
              text: formattedText,
            }],
          };
        } catch (error) {
          // Handle unexpected errors gracefully
          const errorMessage = error instanceof Error ? error.message : "Unknown error";
          return {
            content: [{
              type: "text",
              text: `An error occurred while retrieving issue data:\n${errorMessage}`,
            }],
          };
        }
      }
    }); 
  • Zod schema defining the single required input parameter 'issueId' for the get_issue tool.
    const getIssueSchema = z.object({
      issueId: z.string().describe("The ID of the issue to retrieve"),
    });
  • src/index.ts:31-41 (registration)
    Registration of LinearGetIssueTool (named 'get_issue') among other tools in the MCP server using registerTool.
    registerTool(server, [
      LinearSearchIssuesTool,
      LinearGetProfileTool,
      LinearCreateIssueTool,
      LinearCreateCommentTool,
      LinearUpdateCommentTool,
      LinearGetIssueTool,
      LinearGetTeamIdTool,
      LinearUpdateIssueTool,
      LinearGetCommentTool,
    ]);
  • Helper utility to format the retrieved issue data (including sub-issues and comments) into human-readable output used by the handler.
    function formatIssueToHumanReadable(
      issue: IssueData, 
      comments: IssueComment[], 
      status: StatusData | null,
      subIssues: SubIssueData[]
    ): string {
      if (!issue || !issue.id) {
        return "Invalid or incomplete issue data";
      }
      
      // Build formatted output with simpler structure
      let result = "";
      
      // Basic issue information
      result += `Id: ${issue.id}\n`;
      result += `Title: ${safeText(issue.title)}\n`;
      result += `Description: ${safeText(issue.description, "No description")}\n`;
      
      // Status and priority
      result += `Status: ${status && status.name ? status.name : "Unknown"}\n`;
      result += `Priority: ${getPriorityLabel(issue.priority)}\n`;
      
      // Dates
      result += `Created: ${formatDate(issue.createdAt)}\n`;
      result += `Updated: ${formatDate(issue.updatedAt)}\n`;
      
      // Due date if present
      if (issue.dueDate) {
        result += `Due date: ${formatDate(issue.dueDate)}\n`;
      }
      
      // URL
      result += `Url: ${safeText(issue.url)}\n\n`;
      
      // Sub-issues section
      result += `Sub-issues (${subIssues ? subIssues.length : 0}):\n`;
      
      if (subIssues && subIssues.length > 0) {
        subIssues.forEach((subIssue, index) => {
          result += `#${index + 1}: ${safeText(subIssue.title)}\n`;
          result += `Status: ${subIssue.status && subIssue.status.name ? subIssue.status.name : "Unknown"}\n`;
          result += `Priority: ${getPriorityLabel(subIssue.priority)}\n\n`;
        });
      } else {
        result += "No sub-issues for this issue\n\n";
      }
      
      // Comments section
      result += `Comments (${comments ? comments.length : 0}):\n`;
      
      if (comments && comments.length > 0) {
        comments.forEach((comment, index) => {
          result += `Comment #${index + 1}: ${safeText(comment.body)}\n`;
          result += `Created: ${formatDate(comment.createdAt)}\n\n`;
        });
      } else {
        result += "No comments for this issue\n\n";
      }
      
      return result;
    }

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