Skip to main content
Glama

list_issues

Browse and filter Linear issues by assignee, status, or project to quickly find and manage tickets in your Linear workspace. Supports sorting and customizable limits for efficient issue tracking.

Instructions

List Linear issues (also called tickets) with filtering by assignee, status, and project. Use this to browse and find issues in your Linear workspace.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
assignedToMeNo
assigneeNo
debugNo
limitNo
projectNo
sortByNocreatedAt
sortDirectionNoDESC
statusNo

Implementation Reference

  • Primary ToolHandler for 'list_issues' tool. Processes inputs, creates Linear client, calls helper to fetch issues, formats output as numbered list with details, handles debug and errors.
    const handler = async ( ctx, { assignedToMe, assignee, status, project, sortBy, sortDirection, limit, debug, } ) => { const logger = ctx.effects.logger; try { // Log details about config and parameters logger.debug('List issues called with parameters:', { assignedToMe, assignee, status, project, sortBy, sortDirection, limit, 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 ); // List issues using the Linear SDK client with filters logger.debug('Executing Linear API list with filters'); const results = await listIssues( linearClient, { assignedToMe, assignee, status, project, }, { limit, sortBy, sortDirection, }, logger ); // Log the results count logger.info(`Found ${results.results.length} issues matching criteria`); // Format the output let responseText = ''; logger.info(`Result: ${JSON.stringify(results, null, 2)}`); if (results.results.length === 0) { responseText = 'No issues found matching your criteria.'; } else { responseText = 'Issues found:\n\n'; results.results.forEach((issue, index) => { const priorityMap = { 0: 'No priority', 1: 'Urgent', 2: 'High', 3: 'Medium', 4: 'Low', }; const priority = issue.priority ?? 0; // Format timestamps to be more readable const formatDate = timestamp => { if (!timestamp) return 'Unknown'; const date = new Date(timestamp); return date.toLocaleString(); }; responseText += `${index + 1}. ${issue.title || 'Untitled'}\n`; responseText += ` ID: ${issue.id}\n`; if (issue.identifier) { responseText += ` Identifier: ${issue.identifier}\n`; } if (issue.url) { responseText += ` URL: ${issue.url}\n`; } responseText += ` Status: ${issue.status || 'Unknown'}\n`; responseText += ` Priority: ${priorityMap[priority] || 'Unknown'}\n`; if (issue.project) { responseText += ` Project: ${issue.project.name}\n`; } if (issue.assignee) { responseText += ` Assignee: ${issue.assignee.name}\n`; } responseText += ` Created: ${formatDate(issue.createdAt)}\n`; responseText += ` Updated: ${formatDate(issue.updatedAt)}\n`; responseText += '\n'; }); } logger.debug('Returning formatted list results'); return { content: [{ type: 'text', text: responseText }], }; } catch (error) { logger.error(`Error listing issues: ${error.message}`); logger.error(error.stack); // Create a user-friendly error message with troubleshooting guidance let errorMessage = `Error listing issues: ${error.message}`; // Add detailed diagnostic information if in debug mode if (debug) { errorMessage += '\n\n=== DETAILED DEBUG INFORMATION ==='; // Add filter parameters that were used errorMessage += `\nFilter parameters: - assignedToMe: ${assignedToMe} - assignee: ${assignee || '<not specified>'} - status: ${status || '<not specified>'} - project: ${project || '<not specified>'} - sortBy: ${sortBy} - sortDirection: ${sortDirection} - limit: ${limit}`; // 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 filtering and sorting issues in the list_issues tool.
    const ListIssuesInputSchema = z.object({ assignedToMe: z.boolean().default(false), assignee: z.string().optional(), status: z.string().optional(), project: z.string().optional(), sortBy: z.enum(['createdAt', 'updatedAt']).default('createdAt'), sortDirection: z.enum(['ASC', 'DESC']).default('DESC'), limit: z.number().min(1).max(100).default(25), debug: z.boolean().default(false), // Debug mode to show extra diagnostics });
  • Tool factory using create_tool to register 'list_issues' with name, description, schema, and handler.
    export const ListIssues = create_tool({ name: 'list_issues', description: 'List Linear issues (also called tickets) with filtering by assignee, status, and project. Use this to browse and find issues in your Linear workspace.', inputSchema: ListIssuesInputSchema, handler, });
  • Core helper function that uses Linear SDK to query issues with filters, processes promises for related data (state, assignee, project), and returns validated results.
    async function listIssues( client, filters = {}, { limit = 25, sortBy, sortDirection = 'ASC' } = {}, logger ) { try { logger?.debug('Building Linear SDK filter parameters', { filters, limit, sortBy: sortBy || 'none', sortDirection, }); // Build filters properly using Linear SDK filter syntax const filter = {}; // Add assignee filter if (filters.assignedToMe) { // For assignedToMe we'll use the viewer API directly, // but we'll still build the filter for normal query as fallback filter.assignee = { id: { eq: 'me' } }; } else if (filters.assignee) { filter.assignee = { name: { eq: filters.assignee } }; } // Add status filter if (filters.status) { // Properly filter by state name filter.state = { name: { eq: filters.status } }; logger?.debug(`Filtering by state name: ${filters.status}`); } // Add project filter if (filters.project) { // Filter by project name filter.project = { name: { eq: filters.project } }; logger?.debug(`Filtering by project name: ${filters.project}`); } logger?.debug('Built filter object:', JSON.stringify(filter, null, 2)); // Use the sort direction as provided let direction = sortDirection; logger?.debug(`Using sort direction: ${direction} for field: ${sortBy}`); // Build search parameters const searchParams = { first: limit, }; // Add sort parameters if specified if (sortBy) { searchParams.orderBy = sortBy; searchParams.orderDirection = direction; } // Build the full GraphQL query parameters with proper filter const queryParams = { filter, // Use the properly constructed filter object ...searchParams, }; // Log the complete query parameters for debugging logger?.debug( 'Full Linear API query parameters:', JSON.stringify(queryParams, null, 2) ); let result; try { // First try using the viewer API which may be more reliable for getting assigned issues if (filters.assignedToMe) { logger?.debug( 'Using viewer.assignedIssues API for retrieving assigned issues' ); const me = await client.viewer; result = await me.assignedIssues(searchParams); logger?.debug('Successfully retrieved issues using viewer API'); } else { // Otherwise use the direct GraphQL API from Linear SDK logger?.debug('Using issues API for general issue search'); // @ts-ignore - The Linear SDK types are not accurate for the GraphQL API result = await client.issues(queryParams); logger?.debug('Successfully retrieved issues using issues API'); } } catch (error) { logger?.error(`Linear API error:`, error); throw error; } // Log the raw response structure (without full content) logger?.debug(`Linear API response structure:`, { hasNodes: !!result.nodes, nodeCount: result.nodes?.length || 0, firstNodeKeys: result.nodes?.[0] ? Object.keys(result.nodes[0]) : [], }); // Log search results at debug level logger?.debug(`Linear list returned ${result.nodes.length} results`); // Process issues from Linear SDK format to our domain model // The Linear SDK returns promises for many fields, so we need to await them const processedIssues = []; for (const issue of result.nodes) { try { logger?.debug(`Processing issue ${issue.id}`); // Get state/status information (it's a promise in the Linear SDK) let statusName = 'Unknown'; try { if (issue.state) { const state = await issue.state; if (state && state.name) { statusName = state.name; logger?.debug(`Found state: ${statusName}`); } } } catch (stateError) { logger?.warn(`Error fetching state data: ${stateError.message}`); } // Get assignee if present (it's a promise in the Linear SDK) let assigneeData = undefined; try { if (issue.assignee) { const assignee = await issue.assignee; if (assignee) { assigneeData = { id: assignee.id, name: assignee.name, email: assignee.email, }; logger?.debug(`Found assignee: ${assignee.name}`); } } } catch (assigneeError) { logger?.warn( `Error fetching assignee data: ${assigneeError.message}` ); } // Get project if present (it's a promise in the Linear SDK) let projectData = undefined; try { if (issue.project) { const project = await issue.project; if (project) { projectData = { id: project.id, name: project.name, }; logger?.debug(`Found project: ${project.name}`); } } } catch (projectError) { logger?.warn(`Error fetching project data: ${projectError.message}`); } const processedIssue = IssueSchema.parse({ id: issue.id, identifier: issue.identifier || undefined, // Add the issue identifier (e.g. TEAM-123) url: issue.url || undefined, // Add the issue URL for linking title: issue.title, description: issue.description || undefined, priority: issue.priority, assignee: assigneeData, project: projectData, status: statusName, createdAt: issue.createdAt, updatedAt: issue.updatedAt, }); processedIssues.push(processedIssue); } catch (processError) { logger?.error( `Error processing issue ${issue.id}: ${processError.message}` ); // Continue processing other issues even if one fails } } logger?.debug( `Successfully processed ${processedIssues.length}/${result.nodes.length} issues` ); const issues = processedIssues; // Return the processed results return SearchResultsSchema.parse({ results: issues }); } catch (error) { // Enhanced error logging logger?.error(`Error listing Linear issues: ${error.message}`, { filters, limit, sortBy, sortDirection, 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-118 (registration)
    Instantiation of ListIssues tool with context and inclusion in all_tools array for MCP server registration.
    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), ];

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