linear
MCP skill for linear. Provides 31 tools: get_attachment, create_attachment, delete_attachment, list_comments, save_comment, delete_comment, list_cycles, get_document, list_documents, create_document, update_document, extract_images, get_issue, list_issues, save_issue, list_issue_statuses, get_issue_status, list_issue_labels, create_issue_label, list_projects, get_project, save_project, list_project_labels, list_milestones, get_milestone, save_milestone, list_teams, get_team, list_users, get_user, search_documentation
Authentication
This MCP server uses
OAuth authentication.
The OAuth flow is handled automatically by the MCP client. Tokens are persisted
to
so subsequent runs reuse the same credentials without
re-authenticating.
python
app = LinearApp() # uses default OAuth flow
To bring your own OAuth provider, pass it via the
argument:
python
app = LinearApp(auth=my_oauth_provider)
Dependencies
This skill requires the following Python packages:
Install with uv:
Or with pip:
How to Run
Important: Add
to your Python path so imports resolve correctly:
python
import sys
sys.path.insert(0, ".agents/skills")
from linear.app import LinearApp
Or set the
environment variable:
bash
export PYTHONPATH=".agents/skills:$PYTHONPATH"
Preferred: use (handles dependencies automatically):
bash
PYTHONPATH=.agents/skills uv run --with mcp-skill python -c "
import asyncio
from linear.app import LinearApp
async def main():
app = LinearApp()
result = await app.get_attachment(id="example")
print(result)
asyncio.run(main())
"
Alternative: use directly (install dependencies first):
bash
pip install mcp-skill
PYTHONPATH=.agents/skills python -c "
import asyncio
from linear.app import LinearApp
async def main():
app = LinearApp()
result = await app.get_attachment(id="example")
print(result)
asyncio.run(main())
"
Available Tools
get_attachment
Retrieve an attachment's content by ID.
| Parameter | Type | Required | Description |
|---|
| id | | Yes | Attachment ID |
Example:
python
result = await app.get_attachment(id="example")
create_attachment
Create a new attachment on a specific Linear issue by uploading base64-encoded content.
| Parameter | Type | Required | Description |
|---|
| issue | | Yes | Issue ID or identifier (e.g., LIN-123) |
| base64Content | | Yes | Base64-encoded file content to upload |
| filename | | Yes | Filename for the upload (e.g., 'screenshot.png') |
| contentType | | Yes | MIME type for the upload (e.g., 'image/png', 'application/pdf') |
| title | | No | Optional title for the attachment |
| subtitle | | No | Optional subtitle for the attachment |
Example:
python
result = await app.create_attachment(issue="example", base64Content="example", filename="example")
delete_attachment
Delete an attachment by ID
| Parameter | Type | Required | Description |
|---|
| id | | Yes | Attachment ID |
Example:
python
result = await app.delete_attachment(id="example")
list_comments
List comments for a specific Linear issue
| Parameter | Type | Required | Description |
|---|
| issueId | | Yes | Issue ID |
Example:
python
result = await app.list_comments(issueId="example")
save_comment
Create or update a comment on a Linear issue. If
is provided, updates the existing comment; otherwise creates a new one. When creating,
and
are required.
| Parameter | Type | Required | Description |
|---|
| id | | No | Comment ID. If provided, updates the existing comment |
| issueId | | No | Issue ID (required when creating) |
| parentId | | No | Parent comment ID (for replies, only when creating) |
| body | | Yes | Content as Markdown |
Example:
python
result = await app.save_comment(id="example", issueId="example", parentId="example")
delete_comment
Delete a comment from a Linear issue
| Parameter | Type | Required | Description |
|---|
| id | | Yes | Comment ID |
Example:
python
result = await app.delete_comment(id="example")
list_cycles
Retrieve cycles for a specific Linear team
| Parameter | Type | Required | Description |
|---|
| teamId | | Yes | Team ID |
| type | | No | Filter: current, previous, next, or all |
Example:
python
result = await app.list_cycles(teamId="example", type="example")
get_document
Retrieve a Linear document by ID or slug
| Parameter | Type | Required | Description |
|---|
| id | | Yes | Document ID or slug |
Example:
python
result = await app.get_document(id="example")
list_documents
List documents in the user's Linear workspace
| Parameter | Type | Required | Description |
|---|
| limit | | No | Max results (default 50, max 250) |
| cursor | | No | Next page cursor |
| orderBy | | No | Sort: createdAt |
| query | | No | Search query |
| projectId | | No | Filter by project ID |
| initiativeId | | No | Filter by initiative ID |
| creatorId | | No | Filter by creator ID |
| createdAt | | No | Created after: ISO-8601 date/duration (e.g., -P1D) |
| updatedAt | | No | Updated after: ISO-8601 date/duration (e.g., -P1D) |
| includeArchived | | No | Include archived items |
Example:
python
result = await app.list_documents(limit=1.0, cursor="example", orderBy="example")
create_document
Create a new document in Linear
| Parameter | Type | Required | Description |
|---|
| title | | Yes | Document title |
| content | | No | Content as Markdown |
| project | | No | Project name, ID, or slug |
| issue | | No | Issue ID or identifier (e.g., LIN-123) |
| icon | | No | Icon emoji |
| color | | No | Hex color |
Example:
python
result = await app.create_document(title="example", content="example", project="example")
update_document
Update an existing Linear document
| Parameter | Type | Required | Description |
|---|
| id | | Yes | Document ID or slug |
| title | | No | Document title |
| content | | No | Content as Markdown |
| project | | No | Project name, ID, or slug |
| icon | | No | Icon emoji |
| color | | No | Hex color |
Example:
python
result = await app.update_document(id="example", title="example", content="example")
extract_images
Extract and fetch images from markdown content. Use this to view screenshots, diagrams, or other images embedded in Linear issues, comments, or documents. Pass the markdown content (e.g., issue description) and receive the images as viewable data.
| Parameter | Type | Required | Description |
|---|
| markdown | | Yes | Markdown content containing image references (e.g., issue description, comment body) |
Example:
python
result = await app.extract_images(markdown="example")
get_issue
Retrieve detailed information about an issue by ID, including attachments and git branch name
| Parameter | Type | Required | Description |
|---|
| id | | Yes | Issue ID |
| includeRelations | | No | Include blocking/related/duplicate relations |
| includeCustomerNeeds | | No | Include associated customer needs |
Example:
python
result = await app.get_issue(id="example", includeRelations=True, includeCustomerNeeds=True)
list_issues
List issues in the user's Linear workspace. For my issues, use "me" as the assignee. Use "null" for no assignee.
| Parameter | Type | Required | Description |
|---|
| limit | | No | Max results (default 50, max 250) |
| cursor | | No | Next page cursor |
| orderBy | | No | Sort: createdAt |
| query | | No | Search issue title or description |
| team | | No | Team name or ID |
| state | | No | State type, name, or ID |
| cycle | | No | Cycle name, number, or ID |
| label | | No | Label name or ID |
| assignee | `str | None` | No |
| delegate | | No | Agent name or ID |
| project | | No | Project name, ID, or slug |
| priority | | No | 0=None, 1=Urgent, 2=High, 3=Normal, 4=Low |
| parentId | | No | Parent issue ID |
| createdAt | | No | Created after: ISO-8601 date/duration (e.g., -P1D) |
| updatedAt | | No | Updated after: ISO-8601 date/duration (e.g., -P1D) |
| includeArchived | | No | Include archived items |
Example:
python
result = await app.list_issues(limit=1.0, cursor="example", orderBy="example")
save_issue
Create or update a Linear issue. If
is provided, updates the existing issue; otherwise creates a new one. When creating,
and
are required.
| Parameter | Type | Required | Description |
|---|
| id | | No | Issue ID. If provided, updates the existing issue |
| title | | No | Issue title (required when creating) |
| description | | No | Content as Markdown |
| team | | No | Team name or ID (required when creating) |
| cycle | | No | Cycle name, number, or ID |
| milestone | | No | Milestone name or ID |
| priority | | No | 0=None, 1=Urgent, 2=High, 3=Normal, 4=Low |
| project | | No | Project name, ID, or slug |
| state | | No | State type, name, or ID |
| assignee | `str | None` | No |
| delegate | `str | None` | No |
| labels | | No | Label names or IDs |
| dueDate | | No | Due date (ISO format) |
| parentId | `str | None` | No |
| estimate | | No | Issue estimate value |
| links | | No | Link attachments to add [{url, title}]. Append-only; existing links are never removed |
| blocks | | No | Issue IDs/identifiers this blocks. Append-only; existing relations are never removed |
| blockedBy | | No | Issue IDs/identifiers blocking this. Append-only; existing relations are never removed |
| relatedTo | | No | Related issue IDs/identifiers. Append-only; existing relations are never removed |
| duplicateOf | `str | None` | No |
Example:
python
result = await app.save_issue(id="example", title="example", description="example")
list_issue_statuses
List available issue statuses in a Linear team
| Parameter | Type | Required | Description |
|---|
| team | | Yes | Team name or ID |
Example:
python
result = await app.list_issue_statuses(team="example")
get_issue_status
Retrieve detailed information about an issue status in Linear by name or ID
| Parameter | Type | Required | Description |
|---|
| id | | Yes | Status ID |
| name | | Yes | Status name |
| team | | Yes | Team name or ID |
Example:
python
result = await app.get_issue_status(id="example", name="example", team="example")
list_issue_labels
List available issue labels in a Linear workspace or team
| Parameter | Type | Required | Description |
|---|
| limit | | No | Max results (default 50, max 250) |
| cursor | | No | Next page cursor |
| orderBy | | No | Sort: createdAt |
| name | | No | Filter by name |
| team | | No | Team name or ID |
Example:
python
result = await app.list_issue_labels(limit=1.0, cursor="example", orderBy="example")
create_issue_label
Create a new Linear issue label
| Parameter | Type | Required | Description |
|---|
| name | | Yes | Label name |
| description | | No | Label description |
| color | | No | Hex color code |
| teamId | | No | Team UUID (omit for workspace label) |
| parent | | No | Parent label group name |
| isGroup | | No | Is label group (not directly applicable) |
Example:
python
result = await app.create_issue_label(name="example", description="example", color="example")
list_projects
List projects in the user's Linear workspace
| Parameter | Type | Required | Description |
|---|
| limit | | No | Max results (default 50, max 250) |
| cursor | | No | Next page cursor |
| orderBy | | No | Sort: createdAt |
| query | | No | Search project name |
| state | | No | State type, name, or ID |
| initiative | | No | Initiative name or ID |
| team | | No | Team name or ID |
| member | | No | User ID, name, email, or "me" |
| label | | No | Label name or ID |
| createdAt | | No | Created after: ISO-8601 date/duration (e.g., -P1D) |
| updatedAt | | No | Updated after: ISO-8601 date/duration (e.g., -P1D) |
| includeMilestones | | No | Include milestones |
| includeMembers | | No | Include project members |
| includeArchived | | No | Include archived items |
Example:
python
result = await app.list_projects(limit=1.0, cursor="example", orderBy="example")
get_project
Retrieve details of a specific project in Linear
| Parameter | Type | Required | Description |
|---|
| query | | Yes | Project name, ID, or slug |
| includeMilestones | | No | Include milestones |
| includeMembers | | No | Include project members |
| includeResources | | No | Include resources (documents, links, attachments) |
Example:
python
result = await app.get_project(query="example", includeMilestones=True, includeMembers=True)
save_project
Create or update a Linear project. If
is provided, updates the existing project; otherwise creates a new one. When creating,
and at least one team (via
or
) are required.
| Parameter | Type | Required | Description |
|---|
| id | | No | Project ID. If provided, updates the existing project |
| name | | No | Project name (required when creating) |
| icon | | No | Icon emoji (e.g., :eagle:) |
| color | | No | Hex color |
| summary | | No | Short summary (max 255 chars) |
| description | | No | Content as Markdown |
| state | | No | Project state |
| startDate | | No | Start date (ISO format) |
| targetDate | | No | Target date (ISO format) |
| priority | | No | 0=None, 1=Urgent, 2=High, 3=Medium, 4=Low |
| addTeams | | No | Team name or ID to add |
| removeTeams | | No | Team name or ID to remove |
| setTeams | | No | Replace all teams with these. Cannot combine with addTeams/removeTeams |
| labels | | No | Label names or IDs |
| lead | `str | None` | No |
| addInitiatives | | No | Initiative names/IDs to add |
| removeInitiatives | | No | Initiative names/IDs to remove |
| setInitiatives | | No | Replace all initiatives with these. Cannot combine with addInitiatives/removeInitiatives |
Example:
python
result = await app.save_project(id="example", name="example", icon="example")
list_project_labels
List available project labels in the Linear workspace
| Parameter | Type | Required | Description |
|---|
| limit | | No | Max results (default 50, max 250) |
| cursor | | No | Next page cursor |
| orderBy | | No | Sort: createdAt |
...additional tools omitted for brevity