create_issue
Create a new issue in Linear with title, description, team assignment, due date, priority, status, and optional parent issue linkage for project tracking.
Instructions
A tool that creates an issue in Linear
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| description | Yes | The description of the issue | |
| dueDate | No | The due date of the issue | |
| parentId | No | The ID of the parent issue, used to create a sub-issue | |
| priority | No | The priority of the issue | no_priority |
| status | No | The status of the issue | backlog |
| teamId | Yes | The team ID the issue belongs to | |
| title | Yes | The title of the issue |
Implementation Reference
- src/tools/linear/create-issue.ts:160-331 (handler)Full tool definition for 'create_issue' including name, schema, and complete handler logic that validates inputs, calls linearClient.createIssue, handles response, and formats output.export const LinearCreateIssueTool = createSafeTool({ name: "create_issue", description: "A tool that creates an issue in Linear", schema: createIssueSchema.shape, handler: async (args: z.infer<typeof createIssueSchema>) => { try { // Validate input if (!args.teamId || args.teamId.trim() === "") { return { content: [{ type: "text", text: "Error: Team ID cannot be empty", }], }; } if (!args.title || args.title.trim() === "") { return { content: [{ type: "text", text: "Error: Issue title 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 valid state ID from Linear API if status is provided let stateId: string | undefined; if (args.status) { // 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, args.teamId, linearClient); if (!stateId) { return { content: [{ type: "text", text: `Error: Could not find a valid state ID for "${args.status}" in team ${args.teamId}`, }], }; } } // Create the issue const createIssueResponse = await linearClient.createIssue({ title: args.title, description: args.description, stateId: stateId, dueDate: args.dueDate, priority: priorityValue, teamId: args.teamId, parentId: args.parentId, }); if (!createIssueResponse) { return { content: [{ type: "text", text: "Failed to create issue. Please check your parameters and try again.", }], }; } // Getting issue ID from response // Linear SDK returns results in success and entity pattern if (createIssueResponse.success) { // Access issue and get ID with correct data type const issue = await createIssueResponse.issue; if (issue && issue.id) { return { content: [{ type: "text", text: `Status: Success\nMessage: Linear issue created\nIssue ID: ${issue.id}`, }], }; } } // Extract data from response - fix to handle proper response structure const createResponse = createIssueResponse as unknown as LinearCreateResponse; // Check if the response follows the expected structure with success flag if (createResponse.success === false) { return { content: [{ type: "text", text: "Failed to create issue. Please check your parameters and try again.", }], }; } // Extract issue data from the correct property const issueData: IssueResponseData = createResponse.issue || createIssueResponse as unknown as IssueResponseData; // Directly check the parsed response result const issueId = issueData?.id || (createIssueResponse as unknown as { id?: string })?.id; if (issueId) { return { content: [{ type: "text", text: `Status: Success\nMessage: Linear issue created\nIssue ID: ${issueId}`, }], }; } if (!issueData) { // Display success message even if data is incomplete return { content: [{ type: "text", text: "Status: Success\nMessage: Linear issue created", }], }; } if (!issueData.id) { // Issue data exists but no ID return { content: [{ type: "text", text: "Status: Success\nMessage: Linear issue created (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 created\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 creating the issue:\n${errorMessage}`, }], }; } }, });
- Zod schema definition for create_issue tool inputs.const createIssueSchema = z.object({ teamId: z.string().describe("The team ID the issue belongs to"), title: z.string().describe("The title of the issue"), description: z.string().describe("The description of the issue"), 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"), 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 LinearCreateIssueTool (create_issue) along with other tools in the MCP server main function.registerTool(server, [ LinearSearchIssuesTool, LinearGetProfileTool, LinearCreateIssueTool, LinearCreateCommentTool, LinearUpdateCommentTool, LinearGetIssueTool, LinearGetTeamIdTool, LinearUpdateIssueTool, LinearGetCommentTool, ]);
- Helper function to format the created issue data into human-readable output used in the tool handler.function formatIssueToHumanReadable(issue: IssueResponseData): string { if (!issue || !issue.id) { return "Invalid or incomplete issue data"; } let result = "LINEAR ISSUE CREATED\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`; } // Dates result += `--- TIME INFO ---\n`; 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 created in Linear."; return result; }
- src/tools/linear/tools.ts:12-22 (registration)Re-export of LinearCreateIssueTool from create-issue.js for use in index.ts.export { LinearGetProfileTool, LinearSearchIssuesTool, LinearCreateIssueTool, LinearUpdateIssueTool, LinearCreateCommentTool, LinearUpdateCommentTool, LinearGetIssueTool, LinearGetTeamIdTool, LinearGetCommentTool };