Use MCP servers for Jira and Linear to let AI assistants manage project tickets through natural language. The AI creates issues, updates statuses, assigns team members, and queries sprint progress — all without leaving your IDE or chat interface. Configure community or custom MCP servers that wrap the Jira REST API or Linear GraphQL API and expose project management actions as MCP tools.
Managing Jira and Linear Tickets via MCP
Switching between your code editor and project management tools breaks flow. MCP solves this by bringing ticket management into your AI assistant. Connect a Jira or Linear MCP server, and you can create bugs from error logs, update ticket statuses after completing features, and check sprint progress — all through natural language conversation. This tutorial covers configuring community servers and building a custom Linear MCP server.
Prerequisites
- A Jira Cloud or Linear account with project access
- API token for Jira or API key for Linear
- Claude Desktop or Cursor with MCP support
- Node.js 18+ for custom server development
Step-by-step guide
Configure a Jira MCP server for ticket management
Configure a Jira MCP server for ticket management
Set up a Jira MCP server that connects to your Jira Cloud instance. The server provides tools to search issues with JQL, create new issues, update fields, add comments, and transition statuses. You need a Jira API token (create one at id.atlassian.com/manage-profile/security/api-tokens) and your Jira site URL.
1// Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json2{3 "mcpServers": {4 "jira": {5 "command": "npx",6 "args": ["-y", "mcp-server-jira"],7 "env": {8 "JIRA_URL": "https://your-company.atlassian.net",9 "JIRA_EMAIL": "you@company.com",10 "JIRA_API_TOKEN": "your-jira-api-token"11 }12 }13 }14}1516// Tools typically available:17// - search_issues: Search with JQL queries18// - create_issue: Create a new ticket19// - update_issue: Update fields on existing ticket20// - add_comment: Add a comment to a ticket21// - transition_issue: Change ticket statusExpected result: Jira MCP server connected with tools for searching, creating, and updating tickets.
Configure a Linear MCP server for modern project tracking
Configure a Linear MCP server for modern project tracking
Linear's GraphQL API is well-suited for MCP integration. Several community MCP servers exist for Linear. Configure one with your Linear API key (Settings > API > Personal API keys). Linear's data model is cleaner than Jira's, making AI interactions more straightforward.
1// Claude Desktop config:2{3 "mcpServers": {4 "linear": {5 "command": "npx",6 "args": ["-y", "mcp-server-linear"],7 "env": {8 "LINEAR_API_KEY": "lin_api_your_key_here"9 }10 }11 }12}1314// Common natural language interactions:15// "Create a bug ticket: Login page returns 500 when password is empty"16// "What issues are assigned to me this sprint?"17// "Mark LIN-423 as done"18// "Add a comment to LIN-150: Fixed in PR #78, deployed to staging"Expected result: Linear MCP server connected with issue CRUD tools and sprint query capabilities.
Build a custom Linear MCP server with typed tools
Build a custom Linear MCP server with typed tools
For maximum control, build a custom MCP server that wraps Linear's GraphQL API. This lets you tailor tools to your team's specific workflows — creating issues with custom fields, filtering by team or label, and managing cycles. The Linear GraphQL API is simple and well-documented.
1// src/linear-server.ts2import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";3import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";4import { z } from "zod";56const LINEAR_API = "https://api.linear.app/graphql";7const API_KEY = process.env.LINEAR_API_KEY!;89async function linearQuery(query: string, variables: Record<string, unknown> = {}) {10 const res = await fetch(LINEAR_API, {11 method: "POST",12 headers: {13 "Content-Type": "application/json",14 Authorization: API_KEY,15 },16 body: JSON.stringify({ query, variables }),17 });18 return res.json();19}2021const server = new McpServer({ name: "linear-pm", version: "1.0.0" });2223server.tool("search_issues", "Search Linear issues with filters", {24 query: z.string().optional().describe("Text search query"),25 status: z.string().optional().describe("Filter by status: Todo, In Progress, Done"),26 assignee: z.string().optional().describe("Filter by assignee name"),27 limit: z.number().default(20),28}, async ({ query: q, status, assignee, limit }) => {29 const filters: string[] = [];30 if (q) filters.push(`{ searchableContent: { contains: "${q}" } }`);31 if (status) filters.push(`{ state: { name: { eq: "${status}" } } }`);32 if (assignee) filters.push(`{ assignee: { name: { contains: "${assignee}" } } }`);3334 const filterStr = filters.length > 0 ? `filter: { and: [${filters.join(",")}] },` : "";35 const result = await linearQuery(`{36 issues(${filterStr} first: ${limit}, orderBy: updatedAt) {37 nodes { identifier title state { name } assignee { name } priority createdAt }38 }39 }`);40 return { content: [{ type: "text", text: JSON.stringify(result.data.issues.nodes, null, 2) }] };41});4243server.tool("create_issue", "Create a new Linear issue", {44 title: z.string(),45 description: z.string().optional(),46 teamKey: z.string().describe("Team key, e.g. ENG"),47 priority: z.number().min(0).max(4).default(3).describe("0=none, 1=urgent, 2=high, 3=medium, 4=low"),48 labelNames: z.array(z.string()).optional().describe("Labels to apply"),49}, async ({ title, description, teamKey, priority, labelNames }) => {50 const teamResult = await linearQuery(`{ teams(filter: { key: { eq: "${teamKey}" } }) { nodes { id } } }`);51 const teamId = teamResult.data.teams.nodes[0]?.id;52 if (!teamId) return { content: [{ type: "text", text: `Error: Team ${teamKey} not found` }], isError: true };5354 const result = await linearQuery(`mutation($input: IssueCreateInput!) {55 issueCreate(input: $input) { success issue { identifier title url } }56 }`, { input: { title, description, teamId, priority } });5758 const issue = result.data.issueCreate.issue;59 return { content: [{ type: "text", text: `Created ${issue.identifier}: ${issue.title}\n${issue.url}` }] };60});Expected result: Custom Linear MCP server with search and create tools tailored to your team's workflow.
Integrate project management into your development workflow
Integrate project management into your development workflow
The highest-value pattern is using project management MCP tools alongside code tools. When you finish a feature, tell the AI to update the ticket. When you hit a bug, create a ticket with the error details directly from your conversation. This keeps project tracking up to date without context switching. RapidDev teams use this pattern extensively to keep tickets synchronized with actual development progress.
1// Workflow examples:23// After fixing a bug:4// "Update LIN-423 status to Done and add a comment: 5// Fixed the null pointer in UserService.getProfile(). 6// Root cause was missing null check on the session token. 7// PR #78 deployed to staging."89// After finding a bug while coding:10// "Create a bug ticket for the ENG team: 11// Title: API returns 500 when email contains special characters12// Description: The /api/users/search endpoint throws an unhandled13// error when the email query parameter contains + or & characters.14// Priority: high"1516// Sprint planning:17// "Show me all In Progress issues assigned to me"18// "What high-priority bugs are unassigned in the ENG team?"19// "How many story points are done vs remaining in the current sprint?"Expected result: Seamless project management integrated into your AI-assisted development workflow.
Complete working example
1import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";2import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";3import { z } from "zod";45const API_KEY = process.env.LINEAR_API_KEY!;67async function gql(query: string, variables?: Record<string, unknown>) {8 const res = await fetch("https://api.linear.app/graphql", {9 method: "POST",10 headers: { "Content-Type": "application/json", Authorization: API_KEY },11 body: JSON.stringify({ query, variables }),12 });13 return res.json();14}1516const server = new McpServer({ name: "linear-pm", version: "1.0.0" });1718server.tool("search_issues", "Search Linear issues", {19 query: z.string().optional(), status: z.string().optional(),20 assignee: z.string().optional(), limit: z.number().default(20),21}, async ({ query: q, status, assignee, limit }) => {22 const f: string[] = [];23 if (q) f.push(`searchableContent: { contains: "${q}" }`);24 if (status) f.push(`state: { name: { eq: "${status}" } }`);25 if (assignee) f.push(`assignee: { name: { contains: "${assignee}" } }`);26 const filter = f.length ? `filter: { ${f.join(", ")} },` : "";27 const r = await gql(`{ issues(${filter} first: ${limit}) { nodes { identifier title state { name } assignee { name } priority url } } }`);28 return { content: [{ type: "text", text: JSON.stringify(r.data.issues.nodes, null, 2) }] };29});3031server.tool("create_issue", "Create a new issue", {32 title: z.string(), description: z.string().optional(),33 teamKey: z.string(), priority: z.number().min(0).max(4).default(3),34}, async ({ title, description, teamKey, priority }) => {35 const t = await gql(`{ teams(filter: { key: { eq: "${teamKey}" } }) { nodes { id } } }`);36 const teamId = t.data.teams.nodes[0]?.id;37 if (!teamId) return { content: [{ type: "text", text: "Team not found" }], isError: true };38 const r = await gql(`mutation($i: IssueCreateInput!) { issueCreate(input: $i) { success issue { identifier title url } } }`,39 { i: { title, description, teamId, priority } });40 const issue = r.data.issueCreate.issue;41 return { content: [{ type: "text", text: `Created ${issue.identifier}: ${issue.title}\n${issue.url}` }] };42});4344server.tool("update_status", "Update issue status", {45 issueId: z.string().describe("Issue identifier like ENG-123"),46 status: z.string().describe("New status: Todo, In Progress, Done, Canceled"),47}, async ({ issueId, status }) => {48 const i = await gql(`{ issueSearch(query: "${issueId}") { nodes { id } } }`);49 const id = i.data.issueSearch.nodes[0]?.id;50 if (!id) return { content: [{ type: "text", text: "Issue not found" }], isError: true };51 const states = await gql(`{ workflowStates { nodes { id name } } }`);52 const stateId = states.data.workflowStates.nodes.find((s: any) => s.name === status)?.id;53 if (!stateId) return { content: [{ type: "text", text: `Status '${status}' not found` }], isError: true };54 await gql(`mutation { issueUpdate(id: "${id}", input: { stateId: "${stateId}" }) { success } }`);55 return { content: [{ type: "text", text: `Updated ${issueId} to ${status}` }] };56});5758async function main() {59 await server.connect(new StdioServerTransport());60 console.error("Linear MCP server running");61}62main().catch(e => { console.error(e); process.exit(1); });Common mistakes when using MCP for project management (Jira, Linear)
Why it's a problem: Using personal API tokens instead of service account tokens for shared MCP servers
How to avoid: Create a dedicated service account or bot user for the MCP server. Personal tokens stop working when the user leaves the organization.
Why it's a problem: Not validating team keys before creating issues, causing cryptic API errors
How to avoid: Look up the team ID first and return a clear error if the team key does not exist.
Why it's a problem: Hardcoding status names that differ between projects or teams
How to avoid: Query available workflow states dynamically and match by name. Different teams may have different status names.
Best practices
- Use service account API tokens for MCP servers, not personal tokens
- Include issue URLs in creation and update responses for easy access
- Query workflow states dynamically instead of hardcoding status names
- Add comment tools so the AI can document changes directly on tickets
- Log all project management operations for audit purposes
- Validate team keys and project IDs before creating issues
- Return structured data so the AI can present it clearly to the user
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
Build an MCP server for Linear project management in TypeScript. Include tools to search issues with filters, create new issues with team and priority, update issue status, and add comments. Use the Linear GraphQL API.
Create a Linear MCP server with search_issues, create_issue, and update_status tools. Use the Linear GraphQL API with proper team lookup and workflow state management. Include Zod schemas for all inputs.
Frequently asked questions
Should I use Jira or Linear MCP for my team?
Linear has a cleaner GraphQL API that is easier to work with from MCP. Jira has broader enterprise adoption and more complex workflows. Choose based on what your team already uses.
Can the AI automatically create tickets from errors?
Yes. Combine error monitoring (Sentry MCP) with project management (Linear MCP) to automatically create bug tickets when errors exceed a threshold.
How do I prevent the AI from making unwanted changes to tickets?
Configure the MCP server with read-only tools for querying, and require explicit confirmation before executing create or update operations.
Can RapidDev build custom project management MCP integrations?
Yes. RapidDev builds MCP integrations for Jira, Linear, Asana, Notion, and other project management tools, including custom workflows and automated ticket creation pipelines.
Does this work with Asana or Notion?
The pattern works with any project management tool that has an API. Community MCP servers exist for Notion, and Asana can be integrated with a custom MCP server wrapping their REST API.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation