linear-toon-mcp 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +3 -3
- data/lib/linear_toon_mcp/resolvers.rb +29 -5
- data/lib/linear_toon_mcp/tools/create_issue.rb +10 -7
- data/lib/linear_toon_mcp/tools/get_issue.rb +5 -2
- data/lib/linear_toon_mcp/tools/update_issue.rb +13 -8
- data/lib/linear_toon_mcp/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0daf1704fb96cea3eec8dbd60cb0c51a0facdd3e2ede5c3575b4f312fdde23c6
|
|
4
|
+
data.tar.gz: 8f45de9bd2c1a409b506cd87cfa47479b4aeaf731b0f928d84882eb21aeed9fd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9a1609b91fc4d8a4dd5fa5f46b77fe3bd38cf48a022dd1de9f00432ac29ab55a7c55ada94b0acc9342dc50134890f272db9531a6e6fb7e7e7736f766ef4c58c1
|
|
7
|
+
data.tar.gz: 7b9adcc7084b17bb7167ff469679559a033f217bcb523591bd5a74cee79f51798aa9c5f22695937738f4175bab45691c59da8e16b341f182e53f4f68d74561cf
|
data/README.md
CHANGED
|
@@ -44,7 +44,7 @@ claude mcp add linear-toon -e LINEAR_API_KEY=lin_api_xxxxx -- linear-toon-mcp
|
|
|
44
44
|
|
|
45
45
|
| Tool | Description |
|
|
46
46
|
|------|-------------|
|
|
47
|
-
| `get_issue` | Retrieve a Linear issue by ID or identifier (e.g., `LIN-123`). Returns issue details including title, description, state, assignee, labels, project, and
|
|
47
|
+
| `get_issue` | Retrieve a Linear issue by ID or identifier (e.g., `LIN-123`). Returns issue details including title, description, state, assignee, labels, project, attachments, and the parent and direct child issues (each with identifier, title, state, and url). |
|
|
48
48
|
| `list_issues` | List issues with optional filters (team, assignee, state, label, priority, project, cycle) and cursor-based pagination. Supports name or UUID for most filters. |
|
|
49
49
|
| `list_issue_statuses` | List available workflow states for a team. Returns status id, type (backlog/unstarted/started/completed/canceled), and name. Accepts team name or UUID. |
|
|
50
50
|
| `list_teams` | List all teams in the workspace. Returns team id, name, and key. |
|
|
@@ -53,8 +53,8 @@ claude mcp add linear-toon -e LINEAR_API_KEY=lin_api_xxxxx -- linear-toon-mcp
|
|
|
53
53
|
| `list_projects` | List projects, optionally scoped to a team. Returns project id, name, and state. |
|
|
54
54
|
| `list_cycles` | List cycles for a team. Returns cycle id, name, number, startsAt, and endsAt. Requires team name or UUID. |
|
|
55
55
|
| `get_project` | Retrieve a specific project by name, ID, or slug. Returns project details including state, priority, dates, progress, and lead. Optional includes for members, milestones, and resources. |
|
|
56
|
-
| `create_issue` | Create a new Linear issue. Accepts human-friendly names for team, assignee, state, labels, project, cycle, and milestone (resolved to IDs automatically). Supports issue relations and link attachments. |
|
|
57
|
-
| `update_issue` | Update an existing Linear issue by ID. Supports partial updates, null to remove fields, and relation replacement. |
|
|
56
|
+
| `create_issue` | Create a new Linear issue. Accepts human-friendly names for team, assignee, state, labels, project, cycle, and milestone (resolved to IDs automatically; label names resolve against the target team or workspace-wide labels). Relation params (`blocks`, `relatedTo`, `duplicateOf`) and `parentId` accept either issue UUIDs or human identifiers (e.g., `LIN-123`). Supports issue relations and link attachments. |
|
|
57
|
+
| `update_issue` | Update an existing Linear issue by ID. Supports partial updates, null to remove fields, and relation replacement. Relation params (`blocks`, `relatedTo`, `duplicateOf`) and `parentId` accept either issue UUIDs or human identifiers (e.g., `LIN-123`). Label names resolve against the issue's team or workspace-wide labels. |
|
|
58
58
|
| `create_comment` | Create a comment on a Linear issue. Supports Markdown content and threaded replies via parentId. |
|
|
59
59
|
| `list_comments` | List comments for a specific Linear issue in chronological order. Returns each comment's id, body, author, and timestamps. |
|
|
60
60
|
|
|
@@ -92,21 +92,45 @@ module LinearToonMcp
|
|
|
92
92
|
data.dig("workflowStates", "nodes", 0, "id") or raise Error, "State not found: #{value}"
|
|
93
93
|
end
|
|
94
94
|
|
|
95
|
+
# Resolve a single label name or UUID to its UUID.
|
|
96
|
+
# When +team_id+ is supplied, restricts the lookup to labels scoped to that
|
|
97
|
+
# team or to workspace-wide labels (where the label's team relation is null),
|
|
98
|
+
# so a name like "Bug" matches the right team-scoped label and not a same-named
|
|
99
|
+
# label on another team.
|
|
95
100
|
# @param client [Client]
|
|
96
101
|
# @param value [String] label UUID or name
|
|
102
|
+
# @param team_id [String, nil] optional team UUID to scope the lookup
|
|
97
103
|
# @return [String] label UUID
|
|
98
104
|
# @raise [Error] when label not found
|
|
99
|
-
def resolve_label(client, value)
|
|
105
|
+
def resolve_label(client, value, team_id: nil)
|
|
100
106
|
return value if value.match?(UUID_RE)
|
|
101
|
-
|
|
102
|
-
|
|
107
|
+
|
|
108
|
+
filter = {name: {eqIgnoreCase: value}}
|
|
109
|
+
if team_id
|
|
110
|
+
filter[:or] = [
|
|
111
|
+
{team: {null: true}},
|
|
112
|
+
{team: {id: {eq: team_id}}}
|
|
113
|
+
]
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
data = client.query(LABEL_QUERY, variables: {filter:})
|
|
117
|
+
id = data.dig("issueLabels", "nodes", 0, "id")
|
|
118
|
+
return id if id
|
|
119
|
+
|
|
120
|
+
raise Error, label_not_found_message(value, team_id)
|
|
103
121
|
end
|
|
104
122
|
|
|
105
123
|
# @param client [Client]
|
|
106
124
|
# @param values [Array<String>] label UUIDs or names
|
|
125
|
+
# @param team_id [String, nil] optional team UUID to scope the lookup
|
|
107
126
|
# @return [Array<String>] label UUIDs
|
|
108
|
-
def resolve_labels(client, values)
|
|
109
|
-
values.map { |v| resolve_label(client, v) }
|
|
127
|
+
def resolve_labels(client, values, team_id: nil)
|
|
128
|
+
values.map { |v| resolve_label(client, v, team_id:) }
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def label_not_found_message(value, team_id)
|
|
132
|
+
return "Label not found: #{value}" unless team_id
|
|
133
|
+
"Label not found on target team or workspace: #{value}"
|
|
110
134
|
end
|
|
111
135
|
|
|
112
136
|
# @param client [Client]
|
|
@@ -6,7 +6,10 @@ module LinearToonMcp
|
|
|
6
6
|
module Tools
|
|
7
7
|
# Create a new Linear issue with full parameter support.
|
|
8
8
|
# Resolves human-friendly names to IDs for team, assignee, state, labels,
|
|
9
|
-
# project, cycle, and milestone.
|
|
9
|
+
# project, cycle, and milestone. Relation params (blocks, relatedTo,
|
|
10
|
+
# duplicateOf) and parentId accept either issue UUIDs or human identifiers
|
|
11
|
+
# (e.g., LIN-123); both are passed through to Linear unchanged.
|
|
12
|
+
# Supports post-mutation relations and links.
|
|
10
13
|
class CreateIssue < MCP::Tool
|
|
11
14
|
description "Create a new Linear issue"
|
|
12
15
|
|
|
@@ -30,10 +33,10 @@ module LinearToonMcp
|
|
|
30
33
|
cycle: {type: "string", description: "Cycle name, number, or ID"},
|
|
31
34
|
estimate: {type: "number", description: "Issue estimate value"},
|
|
32
35
|
dueDate: {type: "string", description: "Due date (ISO format)"},
|
|
33
|
-
parentId: {type: "string", description: "Parent issue
|
|
34
|
-
blocks: {type: "array", items: {type: "string"}, description: "Issue
|
|
35
|
-
relatedTo: {type: "array", items: {type: "string"}, description: "Related issue
|
|
36
|
-
duplicateOf: {type: "string", description: "Duplicate
|
|
36
|
+
parentId: {type: "string", description: "Parent issue UUID or identifier (e.g., LIN-123)"},
|
|
37
|
+
blocks: {type: "array", items: {type: "string"}, description: "Issue UUIDs or identifiers this blocks"},
|
|
38
|
+
relatedTo: {type: "array", items: {type: "string"}, description: "Related issue UUIDs or identifiers"},
|
|
39
|
+
duplicateOf: {type: "string", description: "Duplicate-of issue UUID or identifier"},
|
|
37
40
|
milestone: {type: "string", description: "Milestone name or ID"},
|
|
38
41
|
delegate: {type: "string", description: "Agent name or ID"},
|
|
39
42
|
links: {type: "array", items: {type: "object", properties: {url: {type: "string"}, title: {type: "string"}}, required: ["url", "title"]}, description: "Link attachments [{url, title}]"}
|
|
@@ -88,7 +91,7 @@ module LinearToonMcp
|
|
|
88
91
|
resolve_fields(input, client, team_id, **kwargs)
|
|
89
92
|
|
|
90
93
|
data = client.query(MUTATION, variables: {input:})
|
|
91
|
-
result = data["issueCreate"]
|
|
94
|
+
result = data["issueCreate"] or raise Error, "Issue creation failed: no result returned"
|
|
92
95
|
raise Error, "Issue creation failed" unless result["success"]
|
|
93
96
|
|
|
94
97
|
issue = result["issue"]
|
|
@@ -131,7 +134,7 @@ module LinearToonMcp
|
|
|
131
134
|
project: nil, cycle: nil, milestone: nil, delegate: nil, **)
|
|
132
135
|
input[:assigneeId] = Resolvers.resolve_user(client, delegate || assignee) if assignee || delegate
|
|
133
136
|
input[:stateId] = Resolvers.resolve_state(client, team_id, state) if state
|
|
134
|
-
input[:labelIds] = Resolvers.resolve_labels(client, labels) if labels
|
|
137
|
+
input[:labelIds] = Resolvers.resolve_labels(client, labels, team_id:) if labels
|
|
135
138
|
project_id = Resolvers.resolve_project(client, project) if project
|
|
136
139
|
input[:projectId] = project_id if project_id
|
|
137
140
|
input[:cycleId] = Resolvers.resolve_cycle(client, team_id, cycle) if cycle
|
|
@@ -5,9 +5,10 @@ require "toon"
|
|
|
5
5
|
module LinearToonMcp
|
|
6
6
|
module Tools
|
|
7
7
|
# Fetch a single Linear issue by ID or identifier and return it as TOON.
|
|
8
|
-
# Includes metadata, state, assignee, labels, project, team,
|
|
8
|
+
# Includes metadata, state, assignee, labels, project, team, attachments,
|
|
9
|
+
# parent issue, and direct child issues.
|
|
9
10
|
class GetIssue < MCP::Tool
|
|
10
|
-
description "Retrieve a Linear issue by ID"
|
|
11
|
+
description "Retrieve a Linear issue by ID, including its parent and direct child issues"
|
|
11
12
|
|
|
12
13
|
annotations(
|
|
13
14
|
read_only_hint: true,
|
|
@@ -46,6 +47,8 @@ module LinearToonMcp
|
|
|
46
47
|
project { id name }
|
|
47
48
|
team { id name }
|
|
48
49
|
attachments { nodes { id title url } }
|
|
50
|
+
parent { identifier title url state { name } }
|
|
51
|
+
children(first: 50) { nodes { identifier title url state { name } } }
|
|
49
52
|
}
|
|
50
53
|
}
|
|
51
54
|
GRAPHQL
|
|
@@ -5,7 +5,10 @@ require "toon"
|
|
|
5
5
|
module LinearToonMcp
|
|
6
6
|
module Tools
|
|
7
7
|
# Update an existing Linear issue by ID. Supports partial updates,
|
|
8
|
-
# null to remove fields, and relation replacement semantics.
|
|
8
|
+
# null to remove fields, and relation replacement semantics. Relation
|
|
9
|
+
# params (blocks, relatedTo, duplicateOf) and parentId accept either
|
|
10
|
+
# issue UUIDs or human identifiers (e.g., LIN-123); both are passed
|
|
11
|
+
# through to Linear unchanged.
|
|
9
12
|
class UpdateIssue < MCP::Tool
|
|
10
13
|
description "Update an existing Linear issue"
|
|
11
14
|
|
|
@@ -30,10 +33,10 @@ module LinearToonMcp
|
|
|
30
33
|
cycle: {type: "string", description: "Cycle name, number, or ID"},
|
|
31
34
|
estimate: {type: "number", description: "Issue estimate value"},
|
|
32
35
|
dueDate: {type: "string", description: "Due date (ISO format)"},
|
|
33
|
-
parentId: {type: ["string", "null"], description: "Parent issue
|
|
34
|
-
blocks: {type: "array", items: {type: "string"}, description: "Issue
|
|
35
|
-
relatedTo: {type: "array", items: {type: "string"}, description: "Related issue
|
|
36
|
-
duplicateOf: {type: ["string", "null"], description: "Duplicate
|
|
36
|
+
parentId: {type: ["string", "null"], description: "Parent issue UUID or identifier (e.g., LIN-123). Null to remove"},
|
|
37
|
+
blocks: {type: "array", items: {type: "string"}, description: "Issue UUIDs or identifiers this blocks. Replaces existing; omit to keep unchanged"},
|
|
38
|
+
relatedTo: {type: "array", items: {type: "string"}, description: "Related issue UUIDs or identifiers. Replaces existing; omit to keep unchanged"},
|
|
39
|
+
duplicateOf: {type: ["string", "null"], description: "Duplicate-of issue UUID or identifier. Null to remove"},
|
|
37
40
|
milestone: {type: "string", description: "Milestone name or ID"},
|
|
38
41
|
delegate: {type: ["string", "null"], description: "Agent name or ID. Null to remove"},
|
|
39
42
|
links: {type: "array", items: {type: "object", properties: {url: {type: "string"}, title: {type: "string"}}, required: ["url", "title"]}, description: "Link attachments [{url, title}]"}
|
|
@@ -111,7 +114,7 @@ module LinearToonMcp
|
|
|
111
114
|
build_input(input, client, team_id, kwargs)
|
|
112
115
|
|
|
113
116
|
data = client.query(MUTATION, variables: {id:, input:})
|
|
114
|
-
result = data["issueUpdate"]
|
|
117
|
+
result = data["issueUpdate"] or raise Error, "Issue update failed: no result returned"
|
|
115
118
|
raise Error, "Issue update failed" unless result["success"]
|
|
116
119
|
|
|
117
120
|
issue = result["issue"]
|
|
@@ -150,7 +153,7 @@ module LinearToonMcp
|
|
|
150
153
|
end
|
|
151
154
|
|
|
152
155
|
def needs_team_id?(kwargs)
|
|
153
|
-
kwargs.key?(:state) || kwargs.key?(:cycle)
|
|
156
|
+
kwargs.key?(:state) || kwargs.key?(:cycle) || kwargs.key?(:labels)
|
|
154
157
|
end
|
|
155
158
|
|
|
156
159
|
def build_input(input, client, team_id, kwargs)
|
|
@@ -179,7 +182,9 @@ module LinearToonMcp
|
|
|
179
182
|
def add_resolved_fields(input, client, team_id, kwargs)
|
|
180
183
|
input[:teamId] = team_id if kwargs.key?(:team) && team_id
|
|
181
184
|
input[:stateId] = Resolvers.resolve_state(client, team_id, kwargs[:state]) if kwargs.key?(:state) && team_id
|
|
182
|
-
|
|
185
|
+
if kwargs.key?(:labels) && team_id
|
|
186
|
+
input[:labelIds] = Resolvers.resolve_labels(client, kwargs[:labels], team_id:)
|
|
187
|
+
end
|
|
183
188
|
project_id = nil
|
|
184
189
|
if kwargs.key?(:project) && kwargs[:project]
|
|
185
190
|
project_id = Resolvers.resolve_project(client, kwargs[:project])
|