MCP Integration with Linear

MCPLinearTutorialProject Management

Build AI-powered project management workflows by integrating Linear MCP with your agents.

MCP Integration with Linear

Overview

Linear MCP enables AI agents to manage Linear issues, projects, and workflows. This tutorial shows you how to integrate Linear with your AI agents for automated project management.

Prerequisites

  • Linear account with API access
  • Node.js 18+
  • Basic understanding of MCP

Installation

# Install Linear MCP server
npx -y @modelcontextprotocol/server-linear

# Or add to claude_desktop_config.json
{
  "mcpServers": {
    "linear": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-linear"]
    }
  }
}

Configuration

Get API Key

  1. Go to Linear Settings
  2. Navigate to API Keys
  3. Create a new API key
  4. Store securely:
export LINEAR_API_KEY=lin_...

Team Selection

# List available teams
npx -y @modelcontextprotocol/server-linear list-teams

# Note the team ID for subsequent operations

Core Operations

Create Issues

{
  "action": "create_issue",
  "title": "Implement user authentication",
  "team_id": "team_123",
  "description": "Add OAuth2 authentication with Google and GitHub providers",
  "priority": 2,
  "estimate": 5,
  "labels": ["backend", "security"],
  "assignee_id": "user_456"
}

Search Issues

{
  "action": "search_issues",
  "query": "authentication",
  "team_id": "team_123",
  "filter": {
    "status": "in_progress",
    "priority": [1, 2]
  }
}

Update Issues

{
  "action": "update_issue",
  "issue_id": "issue_789",
  "status": "completed",
  "description": "Implementation complete. PR #123 merged."
}

Get Issue Details

{
  "action": "get_issue",
  "issue_id": "issue_789",
  "include_comments": true,
  "include_attachments": false
}

Building AI-Powered Workflows

Automated Issue Triage

# src/linear_triage.py
from mcp import ClientSession
import asyncio

async def triage_new_issue(issue_data: dict):
    """Automatically triage new Linear issues."""
    
    # Analyze issue content
    analysis = await analyze_with_llm(issue_data['description'])
    
    # Suggest team assignment
    suggested_team = map_to_team(analysis['domain'])
    
    # Suggest priority
    suggested_priority = determine_priority(analysis['urgency'])
    
    # Update issue
    await update_issue(
        issue_data['id'],
        team_id=suggested_team,
        priority=suggested_priority,
        labels=analysis['suggested_labels']
    )

async def analyze_with_llm(description: str) -> dict:
    """Use AI to analyze issue description."""
    # Call Claude via MCP or direct API
    ...

Sprint Planning Assistant

# src/sprint_planner.py
async def plan_sprint(team_id: str, capacity: int):
    """Plan next sprint based on team capacity."""
    
    # Get backlog issues
    backlog = await search_issues(
        team_id=team_id,
        filter={'status': 'backlog'}
    )
    
    # Rank by priority and estimate
    ranked = sort_by_priority_value(backlog)
    
    # Select issues fitting capacity
    sprint_issues = select_by_capacity(ranked, capacity)
    
    # Create sprint
    sprint = await create_sprint(
        team_id=team_id,
        name=f"Sprint {get_next_sprint_number()}",
        issue_ids=[i['id'] for i in sprint_issues]
    )
    
    return sprint

Automated Status Updates

# src/status_updates.py
async def sync_with_github(pr_data: dict):
    """Sync Linear issues with GitHub PR status."""
    
    if pr_data['merged']:
        await update_issue(
            issue_id=map_to_linear_issue(pr_data['number']),
            status='completed'
        )
    elif pr_data['closed']:
        await update_issue(
            issue_id=map_to_linear_issue(pr_data['number']),
            status='cancelled'
        )

Advanced Integrations

With GitHub MCP

// Combined MCP config
{
  "mcpServers": {
    "linear": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-linear"]
    },
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"]
    }
  }
}
# src/github_linear_sync.py
async def sync_pr_to_linear(pr_url: str):
    """Sync GitHub PR to Linear issue."""
    
    # Get PR details from GitHub
    pr = await github.get_pull_request(pr_url)
    
    # Find linked Linear issue
    issue_id = extract_linear_id(pr['body'])
    
    # Update Linear issue
    await linear.update_issue(
        issue_id,
        status='in_review',
        description=pr['body']
    )

With Slack MCP

# src/notifications.py
async def notify_issue_created(issue: dict):
    """Send Slack notification for new issue."""
    
    message = f"""
    🎯 New Issue Created
    
    *{issue['title']}*
    Team: {issue['team_name']}
    Priority: {issue['priority']}
    
    <{issue['url']}|View in Linear>
    """
    
    await slack.send_message(
        channel='#engineering',
        text=message
    )

Best Practices

1. Error Handling

async def safe_create_issue(params: dict):
    """Create issue with error handling."""
    try:
        return await linear.create_issue(params)
    except LinearAPIError as e:
        if e.code == 'RATE_LIMIT':
            await asyncio.sleep(60)
            return await linear.create_issue(params)
        raise

2. Batch Operations

async def bulk_create_issues(issues: list[dict]):
    """Create multiple issues efficiently."""
    results = []
    for issue in issues:
        result = await linear.create_issue(issue)
        results.append(result)
        await asyncio.sleep(0.1)  # Rate limit spacing
    return results

3. Caching

from functools import lru_cache

@lru_cache(maxsize=100)
async def get_team(team_id: str):
    """Cache team data to reduce API calls."""
    return await linear.get_team(team_id)

Troubleshooting

Common Issues

Authentication failed:

# Verify API key
export LINEAR_API_KEY=lin_your_key_here

# Test connection
npx -y @modelcontextprotocol/server-linear list-teams

Team not found:

# List all teams first
teams = await linear.list_teams()
print(teams)  # Find correct team_id

Rate limit exceeded:

# Implement retry with backoff
async def with_retry(func, max_retries=3):
    for attempt in range(max_retries):
        try:
            return await func()
        except RateLimitError:
            await asyncio.sleep(2 ** attempt)
    raise

Resources


Last updated: May 2026