linear-toon-mcp 0.3.0 → 0.4.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b25cc1890ddb1f779b8adcd4636e6d4415035c671e91f5da91f5df78d5b03e6
4
- data.tar.gz: ee755802e2a8ab10e7008c0a21779fe1a4058a29ca117d2c56cb9d8761620854
3
+ metadata.gz: dc4a8872f9db60b36c89019d376e06b28c95ea1d881c62996c0cf05e65e8ccb5
4
+ data.tar.gz: 479e37c017575c68fda311020066518211956fe2416d76c16e337114d20d9cc0
5
5
  SHA512:
6
- metadata.gz: cef723735b39f80756443f1a680816633f9a7c1605d68f7a4c4fd177d37c754e3ec5a9328e5c868c726f1d47429ea9ee6f7221aab4a74f917ecb271b67717270
7
- data.tar.gz: 2032d28b1e54dc569a8dccd96e06544056d1c06168e4170d2c1f3e7f1b6c5e7c08a1b4881ca0a546349a97329175f2516e883bcecdf52555b55a1447538cfe60
6
+ metadata.gz: e4967b288c7ee386c448b03acecc0c74b9d7134c88efa23179341a7ab6433e6e58372a84df06a0a835c60bde9522d699a0f57572e56365541521a1161f8e52e5
7
+ data.tar.gz: '078f01ebb0c2b14ba592e065b3bc131768e1ec1d51df887cbae0abd4dcb8b32f48c89e15d54b027d889d6b88ba524c95f8a9ff40685853d1b9244d496f122ffa'
data/README.md CHANGED
@@ -46,6 +46,12 @@ claude mcp add -e LINEAR_API_KEY=lin_api_xxxxx linear-toon -- linear-toon-mcp
46
46
  |------|-------------|
47
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 attachments. |
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
+ | `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
+ | `list_teams` | List all teams in the workspace. Returns team id, name, and key. |
51
+ | `list_users` | List users in the workspace, optionally scoped to a team. Returns user id, name, and email. |
52
+ | `list_issue_labels` | List issue labels, optionally scoped to a team. Returns label id and name. |
53
+ | `list_projects` | List projects, optionally scoped to a team. Returns project id, name, and state. |
54
+ | `list_cycles` | List cycles for a team. Returns cycle id, name, number, startsAt, and endsAt. Requires team name or UUID. |
49
55
  | `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. |
50
56
  | `update_issue` | Update an existing Linear issue by ID. Supports partial updates, null to remove fields, and relation replacement. |
51
57
  | `create_comment` | Create a comment on a Linear issue. Supports Markdown content and threaded replies via parentId. |
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "toon"
4
+
5
+ module LinearToonMcp
6
+ module Tools
7
+ # List cycles for a Linear team.
8
+ # Returns TOON-encoded array of cycles with id, name, number, startsAt, and endsAt.
9
+ class ListCycles < MCP::Tool
10
+ description "List cycles for a team"
11
+
12
+ annotations(
13
+ read_only_hint: true,
14
+ destructive_hint: false,
15
+ idempotent_hint: true
16
+ )
17
+
18
+ input_schema(
19
+ properties: {
20
+ team: {type: "string", description: "Team name or ID"}
21
+ },
22
+ required: ["team"],
23
+ additionalProperties: false
24
+ )
25
+
26
+ QUERY = <<~GRAPHQL
27
+ query($filter: CycleFilter) {
28
+ cycles(filter: $filter) { nodes { id name number startsAt endsAt } }
29
+ }
30
+ GRAPHQL
31
+
32
+ class << self
33
+ # @param team [String] team name or UUID
34
+ # @param server_context [Hash, nil] must contain +:client+ key with a {Client}
35
+ # @return [MCP::Tool::Response] TOON-encoded cycle list or error
36
+ def call(team:, server_context: nil)
37
+ client = server_context&.dig(:client) or raise Error, "client missing from server_context"
38
+ team_id = Resolvers.resolve_team(client, team)
39
+ data = client.query(QUERY, variables: {filter: {team: {id: {eq: team_id}}}})
40
+ cycles = data["cycles"] or raise Error, "Unexpected response: missing cycles field"
41
+ text = Toon.encode(cycles)
42
+ MCP::Tool::Response.new([{type: "text", text:}])
43
+ rescue Error => e
44
+ MCP::Tool::Response.new([{type: "text", text: e.message}], error: true)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "toon"
4
+
5
+ module LinearToonMcp
6
+ module Tools
7
+ # List issue labels in the Linear workspace, optionally scoped to a team.
8
+ # Returns TOON-encoded array of labels with id and name.
9
+ class ListIssueLabels < MCP::Tool
10
+ description "List issue labels, optionally scoped to a team"
11
+
12
+ annotations(
13
+ read_only_hint: true,
14
+ destructive_hint: false,
15
+ idempotent_hint: true
16
+ )
17
+
18
+ input_schema(
19
+ properties: {
20
+ team: {type: "string", description: "Team name or ID"}
21
+ },
22
+ additionalProperties: false
23
+ )
24
+
25
+ QUERY = <<~GRAPHQL
26
+ query($filter: IssueLabelFilter) {
27
+ issueLabels(filter: $filter) { nodes { id name } }
28
+ }
29
+ GRAPHQL
30
+
31
+ class << self
32
+ # @param team [String, nil] team name or UUID (optional scope)
33
+ # @param server_context [Hash, nil] must contain +:client+ key with a {Client}
34
+ # @return [MCP::Tool::Response] TOON-encoded label list or error
35
+ def call(team: nil, server_context: nil)
36
+ client = server_context&.dig(:client) or raise Error, "client missing from server_context"
37
+
38
+ variables = {}
39
+ if team
40
+ team_id = Resolvers.resolve_team(client, team)
41
+ variables[:filter] = {team: {id: {eq: team_id}}}
42
+ end
43
+
44
+ data = client.query(QUERY, variables:)
45
+ labels = data["issueLabels"] or raise Error, "Unexpected response: missing issueLabels field"
46
+ text = Toon.encode(labels)
47
+ MCP::Tool::Response.new([{type: "text", text:}])
48
+ rescue Error => e
49
+ MCP::Tool::Response.new([{type: "text", text: e.message}], error: true)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "toon"
4
+
5
+ module LinearToonMcp
6
+ module Tools
7
+ # List available workflow states for a Linear team.
8
+ # Returns TOON-encoded array of statuses with id, type, and name.
9
+ class ListIssueStatuses < MCP::Tool
10
+ description "List available issue statuses in a Linear team"
11
+
12
+ annotations(
13
+ read_only_hint: true,
14
+ destructive_hint: false,
15
+ idempotent_hint: true
16
+ )
17
+
18
+ input_schema(
19
+ properties: {
20
+ team: {type: "string", description: "Team name or ID"}
21
+ },
22
+ required: ["team"],
23
+ additionalProperties: false
24
+ )
25
+
26
+ QUERY = <<~GRAPHQL
27
+ query($filter: WorkflowStateFilter) {
28
+ workflowStates(filter: $filter) { nodes { id type name } }
29
+ }
30
+ GRAPHQL
31
+
32
+ class << self
33
+ # @param team [String] team name or UUID
34
+ # @param server_context [Hash, nil] must contain +:client+ key with a {Client}
35
+ # @return [MCP::Tool::Response] TOON-encoded status list or error
36
+ def call(team:, server_context: nil)
37
+ client = server_context&.dig(:client) or raise Error, "client missing from server_context"
38
+ team_id = Resolvers.resolve_team(client, team)
39
+ data = client.query(QUERY, variables: {filter: {team: {id: {eq: team_id}}}})
40
+ states = data["workflowStates"] or raise Error, "Unexpected response: missing workflowStates field"
41
+ text = Toon.encode(states)
42
+ MCP::Tool::Response.new([{type: "text", text:}])
43
+ rescue Error => e
44
+ MCP::Tool::Response.new([{type: "text", text: e.message}], error: true)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "toon"
4
+
5
+ module LinearToonMcp
6
+ module Tools
7
+ # List projects in the Linear workspace, optionally scoped to a team.
8
+ # Returns TOON-encoded array of projects with id, name, and state.
9
+ class ListProjects < MCP::Tool
10
+ description "List projects, optionally scoped to a team"
11
+
12
+ annotations(
13
+ read_only_hint: true,
14
+ destructive_hint: false,
15
+ idempotent_hint: true
16
+ )
17
+
18
+ input_schema(
19
+ properties: {
20
+ team: {type: "string", description: "Team name or ID"}
21
+ },
22
+ additionalProperties: false
23
+ )
24
+
25
+ QUERY = <<~GRAPHQL
26
+ query($filter: ProjectFilter) {
27
+ projects(filter: $filter) { nodes { id name state } }
28
+ }
29
+ GRAPHQL
30
+
31
+ class << self
32
+ # @param team [String, nil] team name or UUID (optional scope)
33
+ # @param server_context [Hash, nil] must contain +:client+ key with a {Client}
34
+ # @return [MCP::Tool::Response] TOON-encoded project list or error
35
+ def call(team: nil, server_context: nil)
36
+ client = server_context&.dig(:client) or raise Error, "client missing from server_context"
37
+
38
+ variables = {}
39
+ if team
40
+ team_id = Resolvers.resolve_team(client, team)
41
+ variables[:filter] = {accessibleTeams: {id: {eq: team_id}}}
42
+ end
43
+
44
+ data = client.query(QUERY, variables:)
45
+ projects = data["projects"] or raise Error, "Unexpected response: missing projects field"
46
+ text = Toon.encode(projects)
47
+ MCP::Tool::Response.new([{type: "text", text:}])
48
+ rescue Error => e
49
+ MCP::Tool::Response.new([{type: "text", text: e.message}], error: true)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "toon"
4
+
5
+ module LinearToonMcp
6
+ module Tools
7
+ # List all teams in the Linear workspace.
8
+ # Returns TOON-encoded array of teams with id, name, and key.
9
+ class ListTeams < MCP::Tool
10
+ description "List teams in the workspace"
11
+
12
+ annotations(
13
+ read_only_hint: true,
14
+ destructive_hint: false,
15
+ idempotent_hint: true
16
+ )
17
+
18
+ input_schema(
19
+ properties: {},
20
+ additionalProperties: false
21
+ )
22
+
23
+ QUERY = <<~GRAPHQL
24
+ query {
25
+ teams { nodes { id name key } }
26
+ }
27
+ GRAPHQL
28
+
29
+ class << self
30
+ # @param server_context [Hash, nil] must contain +:client+ key with a {Client}
31
+ # @return [MCP::Tool::Response] TOON-encoded team list or error
32
+ def call(server_context: nil)
33
+ client = server_context&.dig(:client) or raise Error, "client missing from server_context"
34
+ data = client.query(QUERY)
35
+ teams = data["teams"] or raise Error, "Unexpected response: missing teams field"
36
+ text = Toon.encode(teams)
37
+ MCP::Tool::Response.new([{type: "text", text:}])
38
+ rescue Error => e
39
+ MCP::Tool::Response.new([{type: "text", text: e.message}], error: true)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "toon"
4
+
5
+ module LinearToonMcp
6
+ module Tools
7
+ # List users in the Linear workspace, optionally scoped to a team.
8
+ # Returns TOON-encoded array of users with id, name, and email.
9
+ class ListUsers < MCP::Tool
10
+ description "List users, optionally scoped to a team"
11
+
12
+ annotations(
13
+ read_only_hint: true,
14
+ destructive_hint: false,
15
+ idempotent_hint: true
16
+ )
17
+
18
+ input_schema(
19
+ properties: {
20
+ team: {type: "string", description: "Team name or ID"}
21
+ },
22
+ additionalProperties: false
23
+ )
24
+
25
+ QUERY = <<~GRAPHQL
26
+ query {
27
+ users { nodes { id name email } }
28
+ }
29
+ GRAPHQL
30
+
31
+ TEAM_MEMBERS_QUERY = <<~GRAPHQL
32
+ query($id: String!) {
33
+ team(id: $id) { members { nodes { id name email } } }
34
+ }
35
+ GRAPHQL
36
+
37
+ class << self
38
+ # @param team [String, nil] team name or UUID (optional scope)
39
+ # @param server_context [Hash, nil] must contain +:client+ key with a {Client}
40
+ # @return [MCP::Tool::Response] TOON-encoded user list or error
41
+ def call(team: nil, server_context: nil)
42
+ client = server_context&.dig(:client) or raise Error, "client missing from server_context"
43
+
44
+ users = if team
45
+ team_id = Resolvers.resolve_team(client, team)
46
+ data = client.query(TEAM_MEMBERS_QUERY, variables: {id: team_id})
47
+ data.dig("team", "members") or raise Error, "Unexpected response: missing team members field"
48
+ else
49
+ data = client.query(QUERY)
50
+ data["users"] or raise Error, "Unexpected response: missing users field"
51
+ end
52
+
53
+ text = Toon.encode(users)
54
+ MCP::Tool::Response.new([{type: "text", text:}])
55
+ rescue Error => e
56
+ MCP::Tool::Response.new([{type: "text", text: e.message}], error: true)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LinearToonMcp
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
@@ -9,6 +9,12 @@ require_relative "linear_toon_mcp/tools/list_issues"
9
9
  require_relative "linear_toon_mcp/tools/create_comment"
10
10
  require_relative "linear_toon_mcp/tools/create_issue"
11
11
  require_relative "linear_toon_mcp/tools/update_issue"
12
+ require_relative "linear_toon_mcp/tools/list_issue_statuses"
13
+ require_relative "linear_toon_mcp/tools/list_teams"
14
+ require_relative "linear_toon_mcp/tools/list_users"
15
+ require_relative "linear_toon_mcp/tools/list_issue_labels"
16
+ require_relative "linear_toon_mcp/tools/list_projects"
17
+ require_relative "linear_toon_mcp/tools/list_cycles"
12
18
 
13
19
  # Token-efficient MCP server for Linear. Wraps Linear's GraphQL API
14
20
  # and returns TOON-formatted responses for ~40-60% token savings.
@@ -24,7 +30,7 @@ module LinearToonMcp
24
30
  name: "linear-toon-mcp",
25
31
  version: VERSION,
26
32
  description: "Manage Linear issues, projects, and teams",
27
- tools: [Tools::GetIssue, Tools::ListIssues, Tools::CreateComment, Tools::CreateIssue, Tools::UpdateIssue],
33
+ tools: [Tools::GetIssue, Tools::ListIssues, Tools::ListIssueStatuses, Tools::ListTeams, Tools::ListUsers, Tools::ListIssueLabels, Tools::ListProjects, Tools::ListCycles, Tools::CreateComment, Tools::CreateIssue, Tools::UpdateIssue],
28
34
  server_context: {client:}
29
35
  )
30
36
  end
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: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yevhenii Hurin
@@ -69,7 +69,13 @@ files:
69
69
  - lib/linear_toon_mcp/tools/create_comment.rb
70
70
  - lib/linear_toon_mcp/tools/create_issue.rb
71
71
  - lib/linear_toon_mcp/tools/get_issue.rb
72
+ - lib/linear_toon_mcp/tools/list_cycles.rb
73
+ - lib/linear_toon_mcp/tools/list_issue_labels.rb
74
+ - lib/linear_toon_mcp/tools/list_issue_statuses.rb
72
75
  - lib/linear_toon_mcp/tools/list_issues.rb
76
+ - lib/linear_toon_mcp/tools/list_projects.rb
77
+ - lib/linear_toon_mcp/tools/list_teams.rb
78
+ - lib/linear_toon_mcp/tools/list_users.rb
73
79
  - lib/linear_toon_mcp/tools/update_issue.rb
74
80
  - lib/linear_toon_mcp/version.rb
75
81
  homepage: https://github.com/hoblin/linear-toon-mcp