linear-toon-mcp 1.1.0 → 2.0.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: f153b22caa9c1df3508d6ee6e5e515c9233a45bb46e85cfe23dc4293f552eca1
4
- data.tar.gz: 1b28577e054f4aa6759b1cd1a78f6ae189d78c94ec3f39b65abaaf28a4362128
3
+ metadata.gz: 1713851285cfd54dfea2076258f2b51350baef15badde9364b694fd0fbf83412
4
+ data.tar.gz: a7065474403dcc7f735600c8e0b2b6b2790db42e2ad5f6cdeab46881d49c054b
5
5
  SHA512:
6
- metadata.gz: 9edb2ad3b2bdec827ccd31c4bd18466e8d5c2e3ece9b6568d62ef21faa2262ffc7c3f4fdc98053993845485412f0ac2ed59ffb40db7478ff27668513dc716b6c
7
- data.tar.gz: d765472c7885e39684d40ed9543fc73b30e8475c7e62af7cf6818d9bf61acf0fe6d135df2c500f71b54d7456d00e8c6ffac09f1f9dd0a92e5a3993b5e2adb4ec
6
+ metadata.gz: 638a05e2f1d541c8d3564dfb6d13df6d873fc53228130b1bdbe989f7285c300864bf642ba9a8834457a1ad9539e497260ac1e623b4588917a04489c19601733c
7
+ data.tar.gz: d2fa914f39d27822c33b66f9b5abfcae5942a09c167848fd578aeaf911196468120f839228730ad595dbc126d73892895493363e0d50910ea617ad743f4b9816
data/README.md CHANGED
@@ -15,7 +15,7 @@ The official Linear MCP server returns verbose JSON responses that consume signi
15
15
  ## Stack
16
16
 
17
17
  - **[mcp](https://rubygems.org/gems/mcp)** — MCP server framework (stdio transport)
18
- - **[toon-ruby](https://github.com/andrepcg/toon-ruby)** — JSON-to-TOON conversion
18
+ - **[toon-fu](https://github.com/hoblin/toon-fu)** — TOON encoder, versioned by the spec it implements
19
19
  - **Net::HTTP** — Minimal GraphQL client for Linear API (no heavy dependencies)
20
20
 
21
21
  ## Installation
@@ -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
 
@@ -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 the value
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
- filter_type :ProjectStatusFilter
9
- lookup_by :name
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
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "toon"
3
+ require "toon_fu"
4
4
 
5
5
  module LinearToonMcp
6
6
  module Tools
@@ -29,7 +29,7 @@ module LinearToonMcp
29
29
  # Returns a successful MCP response with TOON-encoded +data+.
30
30
  # @return [MCP::Tool::Response]
31
31
  def success_response(data)
32
- MCP::Tool::Response.new([{type: "text", text: Toon.encode(data)}])
32
+ MCP::Tool::Response.new([{type: "text", text: ToonFu.encode(data)}])
33
33
  end
34
34
 
35
35
  # Returns an error MCP response carrying +message+.
@@ -67,7 +67,7 @@ module LinearToonMcp
67
67
  #
68
68
  # @return [MCP::Tool::Response]
69
69
  def respond_with_warnings(data, warnings, context:)
70
- text = Toon.encode(data)
70
+ text = ToonFu.encode(data)
71
71
  text += "\nWARNING (#{context}): #{warnings.join("; ")}" if warnings.any?
72
72
  MCP::Tool::Response.new([{type: "text", text:}])
73
73
  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) && kwargs[: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)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LinearToonMcp
4
- VERSION = "1.1.0"
4
+ VERSION = "2.0.0"
5
5
  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: 1.1.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yevhenii Hurin
@@ -38,19 +38,19 @@ dependencies:
38
38
  - !ruby/object:Gem::Version
39
39
  version: 3.3.1
40
40
  - !ruby/object:Gem::Dependency
41
- name: toon-ruby
41
+ name: toon-fu
42
42
  requirement: !ruby/object:Gem::Requirement
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: '0.1'
46
+ version: 4.1.0
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: '0.1'
53
+ version: 4.1.0
54
54
  description: A Ruby MCP server that wraps Linear's GraphQL API and returns TOON-formatted
55
55
  responses for ~40-60% token savings in LLM workflows.
56
56
  email:
@@ -127,7 +127,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
127
127
  requirements:
128
128
  - - ">="
129
129
  - !ruby/object:Gem::Version
130
- version: '3.2'
130
+ version: '3.3'
131
131
  required_rubygems_version: !ruby/object:Gem::Requirement
132
132
  requirements:
133
133
  - - ">="