name: extended description: | This skill should be used when the user asks to "create Linear document", "write doc in Linear", "add milestone to project", "update Linear document", "delete milestone", or "set milestone target date". Provides write operations for Linear documents and milestones via GraphQL API (extending read-only MCP).
Overview
This skill extends Linear MCP capabilities by adding write operations for Documents and ProjectMilestones. While Linear MCP provides read-only access to documents and no milestone support, this skill enables full CRUD operations via direct GraphQL API calls.
What this skill adds:
- Document creation, updates, and deletion
- Project milestone management (create, update, delete, list, get)
- Direct GraphQL access for advanced operations
Prerequisites:
- Linear API Key from https://linear.app/settings/api
- Set environment variable:
export LINEAR_API_KEY="lin_api_xxxxx" - Optional:
jqfor JSON formatting
Document Operations
Creating a Document
Basic example:
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "mutation DocumentCreate($input: DocumentCreateInput!) { documentCreate(input: $input) { success document { id title url slugId createdAt creator { name } } } }",
"variables": {
"input": {
"title": "API Design Document"
}
}
}'
With content and project:
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "mutation DocumentCreate($input: DocumentCreateInput!) { documentCreate(input: $input) { success document { id title url slugId } } }",
"variables": {
"input": {
"title": "Q4 Roadmap",
"content": "# Q4 Goals\n\n- Launch feature X\n- Improve performance by 30%",
"projectId": "PROJECT_ID_HERE",
"color": "#FF6B6B"
}
}
}'
Available parameters:
title(required): Document titlecontent: Markdown contentprojectId: Attach to projectinitiativeId: Attach to initiativeissueId: Attach to issuecolor: Icon color (hex format)icon: Icon emoji or name (optional, some emojis may not be valid - omit if validation fails)sortOrder: Display order (float)
Updating a Document
Update title and content:
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "mutation DocumentUpdate($id: String!, $input: DocumentUpdateInput!) { documentUpdate(id: $id, input: $input) { success document { id title updatedAt updatedBy { name } } } }",
"variables": {
"id": "DOCUMENT_ID_OR_SLUG",
"input": {
"title": "Updated Title",
"content": "# Updated Content\n\nNew information here."
}
}
}'
Move to trash:
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "mutation DocumentUpdate($id: String!, $input: DocumentUpdateInput!) { documentUpdate(id: $id, input: $input) { success } }",
"variables": {
"id": "DOCUMENT_ID",
"input": {
"trashed": true
}
}
}'
Available update parameters:
title: New titlecontent: New markdown contentcolor: New icon coloricon: New icontrashed: Move to trash (true) or restore (false)projectId: Move to different projectsortOrder: Update display order
Deleting a Document
Permanently delete (archive):
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "mutation DocumentDelete($id: String!) { documentDelete(id: $id) { success } }",
"variables": {
"id": "DOCUMENT_ID"
}
}'
Restore archived document:
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "mutation DocumentUnarchive($id: String!) { documentUnarchive(id: $id) { success entity { id title } } }",
"variables": {
"id": "DOCUMENT_ID"
}
}'
Project Milestone Operations
Creating a Milestone
Basic milestone:
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "mutation ProjectMilestoneCreate($input: ProjectMilestoneCreateInput!) { projectMilestoneCreate(input: $input) { success projectMilestone { id name status progress targetDate project { id name } } } }",
"variables": {
"input": {
"projectId": "PROJECT_ID_HERE",
"name": "Beta Release"
}
}
}'
With description and target date:
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "mutation ProjectMilestoneCreate($input: ProjectMilestoneCreateInput!) { projectMilestoneCreate(input: $input) { success projectMilestone { id name status progress targetDate } } }",
"variables": {
"input": {
"projectId": "PROJECT_ID_HERE",
"name": "MVP Launch",
"description": "# MVP Goals\n\n- Core features complete\n- 10 beta users onboarded",
"targetDate": "2025-06-30"
}
}
}'
Interactive approach (using AskUserQuestion):
When user doesn't specify a target date, use AskUserQuestion to ask:
// Step 1: Ask user for target date
AskUserQuestion({
questions: [{
question: "What is the target date for this milestone?",
header: "Target Date",
multiSelect: false,
options: [
{
label: "End of this month",
description: "Set target date to the last day of current month"
},
{
label: "End of next month",
description: "Set target date to the last day of next month"
},
{
label: "Custom date",
description: "I'll specify a custom date in YYYY-MM-DD format"
},
{
label: "No target date",
description: "Create milestone without a specific target date"
}
]
}]
})
// Step 2: Based on user's answer, construct the mutation
// If custom date selected, prompt for YYYY-MM-DD format
// If no target date, omit targetDate from input
Available parameters:
projectId(required): Parent project IDname(required): Milestone namedescription: Markdown descriptiontargetDate: Target date (YYYY-MM-DD format)sortOrder: Display order (float)
Status values (auto-calculated):
unstarted: No progress yetnext: Next milestone to work onoverdue: Past target datedone: All issues completed
Updating a Milestone
Update name and target date:
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "mutation ProjectMilestoneUpdate($id: String!, $input: ProjectMilestoneUpdateInput!) { projectMilestoneUpdate(id: $id, input: $input) { success projectMilestone { id name status targetDate } } }",
"variables": {
"id": "MILESTONE_ID",
"input": {
"name": "MVP Launch - Extended",
"targetDate": "2025-07-15"
}
}
}'
Available update parameters:
name: New namedescription: New markdown descriptiontargetDate: New target date (YYYY-MM-DD)sortOrder: New display order
Listing Milestones
List all milestones:
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "query ProjectMilestones($first: Int) { projectMilestones(first: $first) { nodes { id name status progress targetDate project { id name } issues { nodes { id title } } } } }",
"variables": {
"first": 50
}
}'
List milestones for specific project:
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "query Project($id: String!) { project(id: $id) { id name projectMilestones { nodes { id name status progress targetDate } } } }",
"variables": {
"id": "PROJECT_ID"
}
}'
Getting a Single Milestone
Detailed milestone info:
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "query ProjectMilestone($id: String!) { projectMilestone(id: $id) { id name description status progress progressHistory currentProgress targetDate createdAt updatedAt project { id name state } issues { nodes { id title state { name type } assignee { name } } } } }",
"variables": {
"id": "MILESTONE_ID"
}
}'
Deleting a Milestone
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "mutation ProjectMilestoneDelete($id: String!) { projectMilestoneDelete(id: $id) { success } }",
"variables": {
"id": "MILESTONE_ID"
}
}'
Moving a Milestone to Another Project
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "mutation ProjectMilestoneMove($id: String!, $input: ProjectMilestoneMoveInput!) { projectMilestoneMove(id: $id, input: $input) { success projectMilestone { id name project { id name } } } }",
"variables": {
"id": "MILESTONE_ID",
"input": {
"projectId": "NEW_PROJECT_ID"
}
}
}'
Usage Guidelines
When to use this skill
Document operations:
- User asks to "create a document" or "write a doc"
- User wants to "update document content"
- User needs to "delete" or "archive" a document
- User wants to "move document to trash" or "restore document"
Milestone operations:
- User asks to "create a milestone" or "add milestone"
- User wants to "set target date for milestone"
- User needs to "update milestone status" or "rename milestone"
- User asks to "list project milestones" or "show milestone progress"
- User wants to "delete milestone" or "move milestone to another project"
IMPORTANT for Milestones:
- Always use AskUserQuestion to ask for targetDate when creating or updating milestones
- Ask the user to provide a target date in YYYY-MM-DD format
- Validate the date format before making the API call
- If user doesn't provide a date, milestone can be created without targetDate (optional)
How to use
-
Always check for LINEAR_API_KEY:
if [ -z "$LINEAR_API_KEY" ]; then echo "Error: LINEAR_API_KEY not set. Get key from https://linear.app/settings/api" exit 1 fi -
Get IDs first:
- Use Linear MCP's
list_projectsto get project IDs - Use Linear MCP's
list_issuesto get issue IDs - Use
list_documentsto get document IDs/slugs
- Use Linear MCP's
-
For milestone operations, use AskUserQuestion:
- When creating a milestone, ask for targetDate using AskUserQuestion tool
- Example question: "What is the target date for this milestone? (YYYY-MM-DD format, or leave empty for no date)"
- Parse the user's response and include in the mutation
- If user provides empty/no date, omit targetDate from the input
-
Handle JSON carefully:
- Escape newlines in markdown: use
\n - Escape quotes: use
\" - For complex content, consider using heredoc or jq
- Escape newlines in markdown: use
-
Check responses:
- Always verify
success: truein mutation responses - If
success: false, check theerrorsarray - Show the document/milestone URL when available
- Always verify
-
Handle icon field carefully:
- The
iconfield is optional for documents - Some emojis may fail validation with "icon is not a valid icon" error
- If icon validation fails, omit the field and retry
- Linear API only accepts certain emojis - no definitive list available
- The
-
Format output for user:
- Use
jqto pretty-print JSON - Extract key fields like
id,url,status - Provide actionable next steps
- Use
Error Handling
Authentication errors:
{
"errors": [
{
"message": "Authentication required",
"extensions": { "code": "UNAUTHENTICATED" }
}
]
}
→ Check if LINEAR_API_KEY is set and valid
Not found errors:
{
"errors": [
{
"message": "Resource not found",
"extensions": { "code": "NOT_FOUND" }
}
]
}
→ Verify the ID exists using list operations first
Validation errors:
{
"data": {
"documentCreate": {
"success": false
}
},
"errors": [
{
"message": "Title is required",
"path": ["documentCreate", "input", "title"]
}
]
}
→ Check required fields are provided
Rate limiting:
{
"errors": [
{
"message": "Rate limit exceeded",
"extensions": { "code": "RATE_LIMITED" }
}
]
}
→ Wait and retry after a few seconds
Icon validation errors:
{
"errors": [
{
"message": "Argument Validation Error",
"extensions": {
"code": "INVALID_INPUT",
"validationErrors": [
{
"property": "icon",
"constraints": {
"customValidation": "icon is not a valid icon"
}
}
]
}
}
]
}
→ Omit the icon field or try a different emoji/icon name. Linear API only accepts certain emojis.
Combining with Linear MCP
This skill works best alongside the official Linear MCP server:
Linear MCP provides (read operations):
list_documents- Get existing documentsget_document- Read document contentlist_projects- Get project IDslist_issues- Get issue IDslist_teams- Get team info
This skill adds (write operations):
documentCreate- Create new documentsdocumentUpdate- Update documentsdocumentDelete- Delete documentsprojectMilestoneCreate- Create milestonesprojectMilestoneUpdate- Update milestonesprojectMilestoneDelete- Delete milestonesprojectMilestonesquery - List milestones (not in MCP)
Typical workflow:
- Use Linear MCP to list projects → Get project ID
- Use this skill to create a document for that project
- Use Linear MCP to verify the document appears in listings
- Use this skill to create milestones for the project
- Use this skill to query milestone progress
Advanced Usage
Using jq for complex operations
Extract just the document URL:
curl -X POST ... | jq -r '.data.documentCreate.document.url'
Format milestone list as table:
curl -X POST ... | jq -r '.data.projectMilestones.nodes[] | [.name, .status, (.progress * 100 | tostring + "%")] | @tsv'
Using heredoc for large content
CONTENT=$(cat <<'EOF'
# Architecture Design
## Overview
System architecture overview here.
## Components
- API Gateway
- Service Mesh
- Data Layer
EOF
)
# Create temp file with Python for proper JSON encoding
TEMP_FILE=$(mktemp)
python3 << PEOF > "$TEMP_FILE"
import json
data = {
"query": """mutation DocumentCreate(\$input: DocumentCreateInput!) {
documentCreate(input: \$input) {
success
document { id url }
}
}""",
"variables": {
"input": {
"title": "Architecture Design",
"content": """$CONTENT"""
}
}
}
print(json.dumps(data, ensure_ascii=False))
PEOF
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d @"$TEMP_FILE" | jq '.'
rm "$TEMP_FILE"
References
For detailed schema information:
- @references/document-schema.md - Complete Document type definitions
- Search patterns:
grep "DocumentCreateInput\|DocumentUpdateInput\|icon\|color" references/document-schema.md
- Search patterns:
- @references/milestone-schema.md - Complete ProjectMilestone type definitions
- Search patterns:
grep "ProjectMilestoneCreateInput\|ProjectMilestoneUpdateInput\|targetDate" references/milestone-schema.md
- Search patterns:
- @references/examples.md - Additional usage examples
- Search patterns:
grep "Example\|mutation\|query" references/examples.md
- Search patterns:
For the original GraphQL schema: