linear-toon-mcp 1.0.0 → 1.1.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 -1
- data/lib/linear_toon_mcp/resolvers/base.rb +2 -1
- data/lib/linear_toon_mcp/resolvers/project_status.rb +24 -2
- data/lib/linear_toon_mcp/tools/delete_project_milestone.rb +37 -0
- data/lib/linear_toon_mcp/tools/save_issue.rb +3 -3
- data/lib/linear_toon_mcp/tools/save_project_milestone.rb +97 -0
- data/lib/linear_toon_mcp/tools.rb +2 -0
- data/lib/linear_toon_mcp/version.rb +1 -1
- data/lib/linear_toon_mcp.rb +1 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2c8f7853b331d24b32b67d2f6f5db8fae9c4833a25470011019463c69c621fac
|
|
4
|
+
data.tar.gz: '058de398a53a8109347de0ac55f75d6407a2a65e07cbdd10cd8a65754ae99009'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bd93c8d45aa51d44ae10b3e83adebc94db4f09588c73483797163b7ea8ee9ee8b7659383b450beab5759449e99c2788899c97430acd466dc16f5ee25adfbd129
|
|
7
|
+
data.tar.gz: 417404d0048ec42ae75cd1d11f445d283ac3bc72f2bfa265f8825e47e7d1da0746ff579236755285efbaf0ea1288340c390d9e6f41df31834059e6522c222464
|
data/README.md
CHANGED
|
@@ -42,7 +42,7 @@ claude mcp add linear-toon -e LINEAR_API_KEY=lin_api_xxxxx -- linear-toon-mcp
|
|
|
42
42
|
|
|
43
43
|
## Tools
|
|
44
44
|
|
|
45
|
-
Most parameters accept either a UUID or a human identifier (issue identifier like `LIN-123`, team key like `VIB`, project/initiative/label name, user email or `"me"`); the server resolves it. `save_*` tools dispatch by `id` presence — provide an `id` to update, omit it to create. All responses are TOON-encoded.
|
|
45
|
+
Most parameters accept either a UUID or a human identifier (issue identifier like `LIN-123`, team key like `VIB`, project/initiative/label name, user email or `"me"`); the server resolves it, and rejects blank values. `save_*` tools dispatch by `id` presence — provide an `id` to update, omit it to create. All responses are TOON-encoded.
|
|
46
46
|
|
|
47
47
|
### Issues
|
|
48
48
|
|
|
@@ -68,6 +68,8 @@ Most parameters accept either a UUID or a human identifier (issue identifier lik
|
|
|
68
68
|
| `get_project` | Retrieve a project by name, ID, or slug. Optional includes for members, milestones, and resources. |
|
|
69
69
|
| `save_project` | Create or update a project. On create, `name` and `teams` (one or more) are required. Resolves names to IDs for teams, lead, members, labels, status, and initiative (initiative links on creation only — use `add_project_to_initiative` afterwards). |
|
|
70
70
|
| `archive_project` | Archive a project (recoverable soft delete). Linear has no hard-delete for projects. |
|
|
71
|
+
| `save_project_milestone` | Create or update a project milestone. On create, `name` and `project` are required. `sortOrder` positions the milestone among its siblings. `project` is create-only — this tool does not move milestones between projects, so it is rejected on update. |
|
|
72
|
+
| `delete_project_milestone` | Delete a project milestone by ID. Issues assigned to it are detached, not deleted. |
|
|
71
73
|
|
|
72
74
|
### Initiatives
|
|
73
75
|
|
|
@@ -140,8 +140,9 @@ module LinearToonMcp
|
|
|
140
140
|
# @param value [String]
|
|
141
141
|
# @param scope [Hash] parent-scope kwargs (e.g. +team_id:+)
|
|
142
142
|
# @return [String] resolved UUID
|
|
143
|
-
# @raise [Error] when no attribute resolves
|
|
143
|
+
# @raise [Error] when +value+ is blank or no attribute resolves it
|
|
144
144
|
def call(value:, **scope)
|
|
145
|
+
raise Error, "#{entity_label} must not be blank" if value.strip.empty?
|
|
145
146
|
new(**scope).resolve(value)
|
|
146
147
|
end
|
|
147
148
|
|
|
@@ -5,8 +5,30 @@ module LinearToonMcp
|
|
|
5
5
|
# Resolves a Linear project status by name (workspace-scoped).
|
|
6
6
|
class ProjectStatus < Base
|
|
7
7
|
connection :projectStatuses
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
# Linear's projectStatuses field accepts no filter argument.
|
|
11
|
+
def query
|
|
12
|
+
@query ||= "query { #{connection_name} { nodes { id name } } }"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Resolves +value+ to a UUID. UUIDs pass through unchanged; otherwise the
|
|
17
|
+
# workspace status list is matched by name, ignoring case.
|
|
18
|
+
#
|
|
19
|
+
# @raise [Error] when no status matches +value+
|
|
20
|
+
def resolve(value)
|
|
21
|
+
return value if value.match?(UUID_RE)
|
|
22
|
+
|
|
23
|
+
status = statuses.find { |node| node["name"].casecmp?(value) }
|
|
24
|
+
status&.fetch("id") || raise(Error, not_found_message(value))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def statuses
|
|
30
|
+
client.query(self.class.query).dig(self.class.connection_name, "nodes") || []
|
|
31
|
+
end
|
|
10
32
|
end
|
|
11
33
|
end
|
|
12
34
|
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LinearToonMcp
|
|
4
|
+
module Tools
|
|
5
|
+
# Delete a project milestone by id. Issues assigned to the milestone
|
|
6
|
+
# are detached, not deleted.
|
|
7
|
+
class DeleteProjectMilestone < Delete
|
|
8
|
+
description "Delete a project milestone (issues are detached, not deleted)"
|
|
9
|
+
|
|
10
|
+
label "Milestone"
|
|
11
|
+
|
|
12
|
+
annotations(
|
|
13
|
+
read_only_hint: false,
|
|
14
|
+
destructive_hint: true,
|
|
15
|
+
idempotent_hint: false
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
input_schema(
|
|
19
|
+
properties: {
|
|
20
|
+
id: {type: "string", description: "Milestone ID"}
|
|
21
|
+
},
|
|
22
|
+
required: ["id"],
|
|
23
|
+
additionalProperties: false
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
MUTATION = <<~GRAPHQL
|
|
27
|
+
mutation($id: String!) {
|
|
28
|
+
projectMilestoneDelete(id: $id) { success entityId }
|
|
29
|
+
}
|
|
30
|
+
GRAPHQL
|
|
31
|
+
|
|
32
|
+
def variables(id:)
|
|
33
|
+
{id: id}
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -27,7 +27,7 @@ module LinearToonMcp
|
|
|
27
27
|
priority: {type: "number", description: "0=None, 1=Urgent, 2=High, 3=Normal, 4=Low"},
|
|
28
28
|
state: {type: "string", description: "State name or ID"},
|
|
29
29
|
labels: {type: "array", items: {type: "string"}, description: "Label names or IDs"},
|
|
30
|
-
project: {type: "string", description: "Project name or ID"},
|
|
30
|
+
project: {type: ["string", "null"], description: "Project name or ID. Null to remove (update only)"},
|
|
31
31
|
cycle: {type: "string", description: "Cycle name, number, or ID"},
|
|
32
32
|
estimate: {type: "number", description: "Issue estimate value"},
|
|
33
33
|
dueDate: {type: "string", description: "Due date (ISO format)"},
|
|
@@ -200,8 +200,8 @@ module LinearToonMcp
|
|
|
200
200
|
input[:labelIds] = Resolvers::IssueLabel.call_many(values: kwargs[:labels], team_id: team_id)
|
|
201
201
|
end
|
|
202
202
|
project_id = nil
|
|
203
|
-
if kwargs.key?(:project)
|
|
204
|
-
project_id = Resolvers::Project.call(value: kwargs[:project])
|
|
203
|
+
if kwargs.key?(:project)
|
|
204
|
+
project_id = Resolvers::Project.call(value: kwargs[:project]) if kwargs[:project]
|
|
205
205
|
input[:projectId] = project_id
|
|
206
206
|
end
|
|
207
207
|
if kwargs.key?(:milestone)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LinearToonMcp
|
|
4
|
+
module Tools
|
|
5
|
+
# Create or update a project milestone. +id+ presence determines
|
|
6
|
+
# create vs update. On create, +name+ and +project+ are required;
|
|
7
|
+
# +project+ is resolved via {Resolvers::Project}. +project+ is
|
|
8
|
+
# create-only — this tool does not move milestones between projects.
|
|
9
|
+
class SaveProjectMilestone < Base
|
|
10
|
+
description "Create or update a project milestone (id presence determines)"
|
|
11
|
+
|
|
12
|
+
annotations(
|
|
13
|
+
read_only_hint: false,
|
|
14
|
+
destructive_hint: false,
|
|
15
|
+
idempotent_hint: false
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
# standard:disable Layout/LineLength
|
|
19
|
+
input_schema(
|
|
20
|
+
properties: {
|
|
21
|
+
id: {type: "string", description: "Milestone ID. If provided, updates the existing milestone; otherwise creates"},
|
|
22
|
+
project: {type: "string", description: "Project name, ID, or slug (required when creating — rejected on update)"},
|
|
23
|
+
name: {type: "string", description: "Milestone name (required when creating)"},
|
|
24
|
+
description: {type: "string", description: "Description as Markdown"},
|
|
25
|
+
targetDate: {type: "string", description: "Target date (ISO format, YYYY-MM-DD)"},
|
|
26
|
+
sortOrder: {type: "number", description: "Position among the project's milestones — lower sorts first"}
|
|
27
|
+
},
|
|
28
|
+
additionalProperties: false
|
|
29
|
+
)
|
|
30
|
+
# standard:enable Layout/LineLength
|
|
31
|
+
|
|
32
|
+
RETURN_FIELDS = <<~GRAPHQL
|
|
33
|
+
id
|
|
34
|
+
name
|
|
35
|
+
description
|
|
36
|
+
targetDate
|
|
37
|
+
sortOrder
|
|
38
|
+
status
|
|
39
|
+
project { id name }
|
|
40
|
+
GRAPHQL
|
|
41
|
+
|
|
42
|
+
CREATE_MUTATION = <<~GRAPHQL
|
|
43
|
+
mutation($input: ProjectMilestoneCreateInput!) {
|
|
44
|
+
projectMilestoneCreate(input: $input) {
|
|
45
|
+
success
|
|
46
|
+
projectMilestone { #{RETURN_FIELDS.strip} }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
GRAPHQL
|
|
50
|
+
|
|
51
|
+
UPDATE_MUTATION = <<~GRAPHQL
|
|
52
|
+
mutation($id: String!, $input: ProjectMilestoneUpdateInput!) {
|
|
53
|
+
projectMilestoneUpdate(id: $id, input: $input) {
|
|
54
|
+
success
|
|
55
|
+
projectMilestone { #{RETURN_FIELDS.strip} }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
GRAPHQL
|
|
59
|
+
|
|
60
|
+
def perform(id: nil, **kwargs)
|
|
61
|
+
id ? update(id, kwargs) : create(kwargs)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def create(kwargs)
|
|
67
|
+
raise Error, "name is required when creating a milestone" unless kwargs[:name]
|
|
68
|
+
project = kwargs[:project] or raise Error, "project is required when creating a milestone"
|
|
69
|
+
|
|
70
|
+
input = build_input(kwargs).merge(projectId: Resolvers::Project.call(value: project))
|
|
71
|
+
submit(CREATE_MUTATION, "projectMilestoneCreate", input:)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def update(id, kwargs)
|
|
75
|
+
if kwargs.key?(:project)
|
|
76
|
+
raise Error, "Cannot pass `project` on update — this tool does not move milestones between projects"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
submit(UPDATE_MUTATION, "projectMilestoneUpdate", id:, input: build_input(kwargs))
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def submit(mutation, mutation_key, **variables)
|
|
83
|
+
data = client.query(mutation, variables:)
|
|
84
|
+
result = data[mutation_key] or raise Error, "Milestone save failed: no result returned"
|
|
85
|
+
raise Error, "Milestone save failed" unless result["success"]
|
|
86
|
+
result["projectMilestone"]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def build_input(kwargs)
|
|
90
|
+
{name: :name, description: :description, targetDate: :targetDate,
|
|
91
|
+
sortOrder: :sortOrder}.each_with_object({}) do |(key, field), input|
|
|
92
|
+
input[field] = kwargs[key] if kwargs.key?(key)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -39,6 +39,8 @@ require_relative "tools/save_status_update"
|
|
|
39
39
|
require_relative "tools/delete_status_update"
|
|
40
40
|
require_relative "tools/save_project"
|
|
41
41
|
require_relative "tools/archive_project"
|
|
42
|
+
require_relative "tools/save_project_milestone"
|
|
43
|
+
require_relative "tools/delete_project_milestone"
|
|
42
44
|
require_relative "tools/get_team"
|
|
43
45
|
require_relative "tools/get_user"
|
|
44
46
|
require_relative "tools/get_issue_status"
|
data/lib/linear_toon_mcp.rb
CHANGED
|
@@ -42,6 +42,7 @@ module LinearToonMcp
|
|
|
42
42
|
Tools::ListStatusUpdates, Tools::GetStatusUpdate,
|
|
43
43
|
Tools::SaveStatusUpdate, Tools::DeleteStatusUpdate,
|
|
44
44
|
Tools::SaveProject, Tools::ArchiveProject,
|
|
45
|
+
Tools::SaveProjectMilestone, Tools::DeleteProjectMilestone,
|
|
45
46
|
Tools::GetTeam, Tools::GetUser, Tools::GetIssueStatus,
|
|
46
47
|
Tools::CreateIssueLabel
|
|
47
48
|
]
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: linear-toon-mcp
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yevhenii Hurin
|
|
@@ -85,6 +85,7 @@ files:
|
|
|
85
85
|
- lib/linear_toon_mcp/tools/delete.rb
|
|
86
86
|
- lib/linear_toon_mcp/tools/delete_comment.rb
|
|
87
87
|
- lib/linear_toon_mcp/tools/delete_initiative.rb
|
|
88
|
+
- lib/linear_toon_mcp/tools/delete_project_milestone.rb
|
|
88
89
|
- lib/linear_toon_mcp/tools/delete_status_update.rb
|
|
89
90
|
- lib/linear_toon_mcp/tools/get.rb
|
|
90
91
|
- lib/linear_toon_mcp/tools/get_initiative.rb
|
|
@@ -110,6 +111,7 @@ files:
|
|
|
110
111
|
- lib/linear_toon_mcp/tools/save_initiative.rb
|
|
111
112
|
- lib/linear_toon_mcp/tools/save_issue.rb
|
|
112
113
|
- lib/linear_toon_mcp/tools/save_project.rb
|
|
114
|
+
- lib/linear_toon_mcp/tools/save_project_milestone.rb
|
|
113
115
|
- lib/linear_toon_mcp/tools/save_status_update.rb
|
|
114
116
|
- lib/linear_toon_mcp/version.rb
|
|
115
117
|
homepage: https://github.com/hoblin/linear-toon-mcp
|