Skip to main content
Glama
scoutos

Linear MCP Server

by scoutos

add_comment

Add a comment to a Linear ticket by specifying the ticket ID and comment text. Use this tool to provide updates, feedback, or additional details on existing tickets.

Instructions

Add a comment to a specific Linear ticket. This tool is useful for providing feedback, status updates, or additional information on existing tickets.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
commentYesThe comment text to add to the ticket
debugNoDebug mode to show extra diagnostics
ticketIdYesThe ID of the Linear ticket to comment on

Implementation Reference

  • The main handler function for the 'add_comment' tool. It processes input parameters, creates a Linear client, calls the addComment helper, formats the success response or detailed error message, and returns MCP-compatible content.
    const handler = async (ctx, { ticketId, comment, debug }) => {
      const logger = ctx.effects.logger;
    
      try {
        // Log details about config and parameters
        logger.debug('Add comment called with parameters:', {
          ticketId,
          commentLength: comment?.length,
          debug,
        });
    
        // Debug log for API key (masked)
        const apiKey = ctx.config.linearApiKey || '';
        const maskedKey = apiKey
          ? apiKey.substring(0, 4) + '...' + apiKey.substring(apiKey.length - 4)
          : '<not set>';
        logger.debug(`Using Linear API key: ${maskedKey}`);
    
        if (!ctx.config.linearApiKey) {
          throw new Error('LINEAR_API_KEY is not configured');
        }
    
        // Create a Linear client using our effect
        logger.debug('Creating Linear client');
        const linearClient = ctx.effects.linear.createClient(
          ctx.config.linearApiKey
        );
    
        // Add the comment using the Linear SDK client
        logger.debug('Executing Linear API to add comment');
        const result = await addComment(linearClient, ticketId, comment, logger);
    
        // Log that we created the comment
        logger.info(`Added comment to ticket ID: ${ticketId}`);
    
        // Format the output
        const formatDate = timestamp => {
          if (!timestamp) return 'Just now';
          const date = new Date(timestamp);
          return date.toLocaleString();
        };
    
        let responseText = '';
        responseText += `✅ Comment added successfully to ticket ${ticketId}\n\n`;
    
        // Add comment details
        responseText += `**Comment ID:** ${result.id}\n`;
    
        if (result.user) {
          responseText += `**Posted by:** ${result.user.name}\n`;
        }
    
        responseText += `**Posted at:** ${formatDate(result.createdAt)}\n\n`;
        responseText += `**Comment:**\n${result.body}\n`;
    
        logger.debug('Returning formatted comment result');
        return {
          content: [{ type: 'text', text: responseText }],
        };
      } catch (error) {
        logger.error(`Error adding comment: ${error.message}`);
        logger.error(error.stack);
    
        // Create a user-friendly error message with troubleshooting guidance
        let errorMessage = `Error adding comment: ${error.message}`;
    
        // Add detailed diagnostic information if in debug mode
        if (debug) {
          errorMessage += '\n\n=== DETAILED DEBUG INFORMATION ===';
    
          // Add parameters that were used
          errorMessage += `\nParameters:
    - ticketId: ${ticketId}
    - comment length: ${comment?.length} characters`;
    
          // Check if API key is configured
          const apiKey = ctx.config.linearApiKey || '';
          const keyStatus = apiKey
            ? `API key is configured (${apiKey.substring(
                0,
                4
              )}...${apiKey.substring(apiKey.length - 4)})`
            : 'API key is NOT configured - set LINEAR_API_KEY';
    
          errorMessage += `\n\nLinear API Status: ${keyStatus}`;
    
          // Add error details
          if (error.name) {
            errorMessage += `\nError type: ${error.name}`;
          }
    
          if (error.code) {
            errorMessage += `\nError code: ${error.code}`;
          }
    
          if (error.stack) {
            errorMessage += `\n\nStack trace: ${error.stack
              .split('\n')
              .slice(0, 3)
              .join('\n')}`;
          }
    
          // Add Linear API info for manual testing
          errorMessage += `\n\nLinear API: Using official Linear SDK (@linear/sdk)
    For manual testing, try using the SDK directly or the Linear API Explorer in the Linear UI.`;
        }
    
        // Add a note that debug mode can be enabled for more details
        if (!debug) {
          errorMessage += `\n\nFor more detailed diagnostics, retry with debug:true in the input.`;
        }
    
        return {
          content: [
            {
              type: 'text',
              text: errorMessage,
            },
          ],
          isError: true,
        };
      }
    };
  • Zod input schema defining parameters for the add_comment tool: ticketId (string), comment (string), debug (boolean, optional).
    const AddCommentInputSchema = z.object({
      ticketId: z.string().describe('The ID of the Linear ticket to comment on'),
      comment: z.string().describe('The comment text to add to the ticket'),
      debug: z
        .boolean()
        .default(false)
        .describe('Debug mode to show extra diagnostics'),
    });
  • Tool factory using create_tool() that registers the 'add_comment' tool with its name, description, input schema, and handler function.
    export const AddComment = create_tool({
      name: 'add_comment',
      description:
        'Add a comment to a specific Linear ticket. This tool is useful for providing feedback, status updates, or additional information on existing tickets.',
      inputSchema: AddCommentInputSchema,
      handler,
    });
  • Helper function that performs the actual Linear API call to add a comment to a ticket using the Linear SDK, including validation and error handling.
    async function addComment(client, ticketId, comment, logger) {
      try {
        logger?.debug(`Adding comment to Linear issue with ID: ${ticketId}`);
    
        // First verify the issue exists
        const issue = await client.issue(ticketId);
    
        if (!issue) {
          logger?.error(`Issue with ID ${ticketId} not found`);
          throw new Error(`Issue with ID ${ticketId} not found`);
        }
    
        logger?.debug(`Successfully found issue: ${issue.id}, adding comment`);
    
        // Create the comment using the Linear SDK
        // The Linear SDK expects a CommentCreateInput object
        const commentResult = await client.createComment({
          // The issueId property is part of CommentCreateInput
          issueId: ticketId,
          // The body property is part of CommentCreateInput
          body: comment,
        });
    
        if (!commentResult) {
          throw new Error('Failed to create comment, received null response');
        }
    
        // CommentPayload contains a comment property that's a promise
        const commentData = await commentResult.comment;
    
        if (!commentData) {
          throw new Error('Failed to retrieve comment data from response');
        }
    
        logger?.debug(`Successfully added comment: ${commentData.id}`);
    
        // Get current user information for the response
        const me = await client.viewer;
        let userData = undefined;
    
        if (me) {
          userData = {
            id: me.id,
            name: me.name,
            email: me.email,
          };
        }
    
        // Process and validate the comment data using our schema
        const processedComment = CommentSchema.parse({
          id: commentData.id,
          body: commentData.body,
          user: userData,
          createdAt:
            commentData.createdAt instanceof Date
              ? commentData.createdAt.toISOString()
              : commentData.createdAt,
          updatedAt:
            commentData.updatedAt instanceof Date
              ? commentData.updatedAt.toISOString()
              : commentData.updatedAt,
        });
    
        return processedComment;
      } catch (error) {
        // Enhanced error logging
        logger?.error(`Error adding comment to Linear issue: ${error.message}`, {
          ticketId,
          commentLength: comment?.length,
          stack: error.stack,
        });
    
        // Check if it's a Zod validation error (formatted differently)
        if (error.name === 'ZodError') {
          logger?.error(
            'Zod validation error details:',
            JSON.stringify(error.errors, null, 2)
          );
        }
    
        // Rethrow the error for the tool to handle
        throw error;
      }
    }
  • src/index.js:109-149 (registration)
    Instantiation of the AddComment tool instance with context and registration loop that adds all tools (including add_comment) to the MCP server using server.tool().
    const all_tools = [
      new tools.ListIssues(toolContext),
      new tools.GetIssue(toolContext),
      new tools.ListMembers(toolContext),
      new tools.ListProjects(toolContext),
      new tools.GetProject(toolContext),
      new tools.ListTeams(toolContext),
      new tools.AddComment(toolContext),
      new tools.CreateIssue(toolContext),
    ];
    
    // Register tools with the MCP server
    for (const tool of all_tools) {
      server.tool(
        tool.name,
        tool.description,
        tool.inputSchema.shape ?? {},
        async args => {
          try {
            // Call our tool
            const result = await tool.call(args);
    
            // Return format expected by MCP SDK
            return {
              content: result.content,
              error: result.isError
                ? {
                    message: result.content[0]?.text || 'An error occurred',
                  }
                : undefined,
            };
          } catch (error) {
            logger.error(`Error executing tool ${tool.name}: ${error.message}`);
            return {
              content: [{ type: 'text', text: `Error: ${error.message}` }],
              error: { message: error.message },
            };
          }
        }
      );
    }
Behavior2/5

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

No annotations are provided, so the description carries full burden. It states the tool 'adds a comment' (implying a write/mutation operation) but doesn't disclose behavioral traits like authentication requirements, rate limits, error handling, or whether comments are editable/deletable. This is inadequate for a mutation tool with zero annotation coverage.

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

Conciseness4/5

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

The description is appropriately sized (two sentences) and front-loaded with the core purpose. The second sentence adds useful context without being redundant. It could be slightly more concise by merging ideas, but it's efficient overall.

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 this is a mutation tool (adding comments) with no annotations, no output schema, and 3 parameters, the description is incomplete. It doesn't cover error cases, response format, or side effects, leaving significant gaps for an AI agent to use it correctly.

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 description coverage is 100%, so the schema already documents all parameters (ticketId, comment, debug). The description adds no additional meaning beyond what's in the schema (e.g., no examples, format details, or constraints). Baseline 3 is appropriate when schema does the heavy lifting.

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 action ('Add a comment') and target resource ('to a specific Linear ticket'), distinguishing it from sibling tools like create_issue or get_issue. However, it doesn't explicitly differentiate from hypothetical comment-related siblings (none exist in the provided list), so it's not a perfect 5.

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 provides implied usage context ('useful for providing feedback, status updates, or additional information on existing tickets'), which suggests when to use it. However, it lacks explicit guidance on when not to use it (e.g., vs. updating ticket fields directly) or alternatives, and doesn't mention prerequisites like needing write permissions.

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

Related 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/scoutos/mcp-linear'

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