cf-mcp 0.15.5 → 0.16.2

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: 24dc9506e66606bf2146f17aad18cae3a5c1e0a021b5a9d1a1cfd7d3fa08e730
4
- data.tar.gz: 89faa9c9e1fbae4fad23b4b2bf0d9a42b671d214865261c13824d71c2b5aae04
3
+ metadata.gz: 8616f673c57b67cd09602a2b49611c030fc058d63f1701dd2cec2cff2c04d4ad
4
+ data.tar.gz: abc2b55c27a9f4ace220d5fd9d013cb5944c02d8bd6f43106be141ac05aaa208
5
5
  SHA512:
6
- metadata.gz: 8ee6f579771136d11d7f748fb1b0d4cc851765bb7c8fc771cf8b3e4e951a86e6b0b33ff719c14cbf152fa72b1851094fa484d23353c0e67ca5ec00d3e0ae999b
7
- data.tar.gz: 755ce736815559983d1a5ca8b593119b184661a30668dca28551e88a9e544af67553d2519b76359e6bde9b33aac2fa5e7aedbed5498b0d8a7da6af34b0e5d1bc
6
+ metadata.gz: 7587ed21fd3fe5e3f81a7fd574cc32afc3da7c69b7c9869241508ad8a3f477ec813297f1d893b0dba5c3c9ccc4411773adacce35eeb8e15a936328f613d55186
7
+ data.tar.gz: 02e5f832b1c52dc25ec415d362585e7f104a526a24e7454af38192f48d60abc209c7f91175b60dd5759b0dc1670508fdf16ad61fa7db4aebe25edf7f406d984a
data/CHANGELOG.md CHANGED
@@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.16.2] - 2026-01-31
9
+
10
+ ### Fixed
11
+
12
+ - Enum entries now correctly prefixed with `CF_` (e.g., `CF_KEY_SPACE` instead of `KEY_SPACE`) to match the CF_ENUM macro expansion
13
+
14
+ ## [0.16.1] - 2026-01-31
15
+
16
+ ### Fixed
17
+
18
+ - Fix broken logo image path in dashboard (use favicon.svg instead of non-existent logo.svg)
19
+
20
+ ### Changed
21
+
22
+ - Add workflow_dispatch trigger to fly-deploy.yml for manual deployments
23
+
24
+ ## [0.16.0] - 2026-01-26
25
+
26
+ ### Changed
27
+
28
+ - **Refactored tool loading with Index singleton** - Index is now a singleton, eliminating duplicate schema definitions and simplifying tool initialization
29
+ - Tool classes now use `Index.instance.categories` directly in schema definitions instead of requiring runtime configuration
30
+ - Removed `TOOLS` constant and `configure_tool_schemas` method from Server
31
+ - Tools use autoload for lazy loading, ensuring they see populated categories at load time
32
+
8
33
  ## [0.15.5] - 2026-01-26
9
34
 
10
35
  ### Changed
@@ -236,6 +261,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
236
261
  - `cf_list_category` - List items by category
237
262
  - `cf_get_details` - Get full documentation by name
238
263
 
264
+ [0.16.2]: https://github.com/pusewicz/cf-mcp/compare/v0.16.1...v0.16.2
265
+ [0.16.1]: https://github.com/pusewicz/cf-mcp/compare/v0.16.0...v0.16.1
266
+ [0.16.0]: https://github.com/pusewicz/cf-mcp/compare/v0.15.5...v0.16.0
239
267
  [0.15.5]: https://github.com/pusewicz/cf-mcp/compare/v0.15.4...v0.15.5
240
268
  [0.15.4]: https://github.com/pusewicz/cf-mcp/compare/v0.15.3...v0.15.4
241
269
  [0.15.3]: https://github.com/pusewicz/cf-mcp/compare/v0.15.2...v0.15.3
data/Manifest.txt CHANGED
@@ -35,10 +35,7 @@ lib/cf/mcp/tools/list_topics.rb
35
35
  lib/cf/mcp/tools/member_search.rb
36
36
  lib/cf/mcp/tools/parameter_search.rb
37
37
  lib/cf/mcp/tools/response_helpers.rb
38
- lib/cf/mcp/tools/search_enums.rb
39
- lib/cf/mcp/tools/search_functions.rb
40
38
  lib/cf/mcp/tools/search_result_formatter.rb
41
- lib/cf/mcp/tools/search_structs.rb
42
39
  lib/cf/mcp/tools/search_tool.rb
43
40
  lib/cf/mcp/topic_parser.rb
44
41
  lib/cf/mcp/version.rb
data/Rakefile CHANGED
@@ -9,7 +9,7 @@ require "standard/rake"
9
9
 
10
10
  desc "Generate Manifest.txt from git ls-files"
11
11
  task :manifest do
12
- ignore_patterns = %w[bin/ Gemfile .gitignore test/ .github/ .standard.yml cf-mcp.gemspec .ruby-version CLAUDE.md AGENTS.md fly.toml Procfile Dockerfile .dockerignore .claude/]
12
+ ignore_patterns = %w[bin/ Gemfile .gitignore test/ .github/ .standard.yml cf-mcp.gemspec .ruby-version CLAUDE.md AGENTS.md fly.toml Procfile Dockerfile .dockerignore .claude/ .mcp.json]
13
13
 
14
14
  files = IO.popen(%w[git ls-files -z], chdir: __dir__, err: IO::NULL) do |ls|
15
15
  ls.readlines("\x0", chomp: true)
data/lib/cf/mcp/index.rb CHANGED
@@ -1,11 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "singleton"
4
+
3
5
  module CF
4
6
  module MCP
5
7
  class Index
8
+ include Singleton
9
+
6
10
  attr_reader :items, :by_type, :by_category, :topic_references
7
11
 
8
12
  def initialize
13
+ reset!
14
+ end
15
+
16
+ def reset!
9
17
  @items = {}
10
18
  @by_type = {
11
19
  function: [],
@@ -18,7 +18,8 @@ module CF
18
18
 
19
19
  def build
20
20
  parser = Parser.new
21
- index = Index.new
21
+ index = Index.instance
22
+ index.reset!
22
23
 
23
24
  parser.parse_directory(headers_path).each do |item|
24
25
  index.add(item)
data/lib/cf/mcp/parser.rb CHANGED
@@ -236,7 +236,8 @@ module CF
236
236
  # Find the #define block with CF_ENUM macros
237
237
  # Pattern: /* @entry description */ followed by CF_ENUM(NAME, VALUE)
238
238
  content.scan(%r{/\*\s*@entry\s+(.*?)\s*\*/\s*\\?\s*CF_ENUM\s*\(\s*(\w+)\s*,\s*([^)]*)\)}m) do |description, name, value|
239
- entries << Models::EnumDoc::Entry.new(name.strip, value.strip, description.strip)
239
+ # CF_ENUM(K, V) expands to CF_##K = V, so add CF_ prefix
240
+ entries << Models::EnumDoc::Entry.new("CF_#{name.strip}", value.strip, description.strip)
240
241
  end
241
242
 
242
243
  entries
data/lib/cf/mcp/server.rb CHANGED
@@ -1,37 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "mcp"
4
- require_relative "tools/search_tool"
5
- require_relative "tools/search_functions"
6
- require_relative "tools/search_structs"
7
- require_relative "tools/search_enums"
8
- require_relative "tools/list_category"
9
- require_relative "tools/get_details"
10
- require_relative "tools/find_related"
11
- require_relative "tools/parameter_search"
12
- require_relative "tools/member_search"
13
- require_relative "tools/list_topics"
14
- require_relative "tools/get_topic"
15
4
 
16
5
  module CF
17
6
  module MCP
18
7
  class Server
19
8
  attr_reader :server, :index
20
9
 
21
- TOOLS = [
22
- Tools::SearchTool,
23
- Tools::SearchFunctions,
24
- Tools::SearchStructs,
25
- Tools::SearchEnums,
26
- Tools::ListCategory,
27
- Tools::GetDetails,
28
- Tools::FindRelated,
29
- Tools::ParameterSearch,
30
- Tools::MemberSearch,
31
- Tools::ListTopics,
32
- Tools::GetTopic
33
- ].freeze
34
-
35
10
  CORS_HEADERS = {
36
11
  "access-control-allow-origin" => "*",
37
12
  "access-control-allow-methods" => "GET, POST, DELETE, OPTIONS",
@@ -65,6 +40,7 @@ module CF
65
40
 
66
41
  def initialize(index)
67
42
  @index = index
43
+
68
44
  configuration = ::MCP::Configuration.new(protocol_version: PROTOCOL_VERSION)
69
45
  @server = ::MCP::Server.new(
70
46
  name: "cf-mcp",
@@ -76,7 +52,16 @@ module CF
76
52
  ::MCP::Icon.new(src: "#{WEBSITE_URL}/favicon.svg", mime_type: "image/svg+xml", sizes: ["any"]),
77
53
  ::MCP::Icon.new(src: "#{WEBSITE_URL}/favicon-96x96.png", mime_type: "image/png", sizes: ["96x96"])
78
54
  ],
79
- tools: TOOLS,
55
+ tools: [
56
+ Tools::SearchTool,
57
+ Tools::ListCategory,
58
+ Tools::GetDetails,
59
+ Tools::FindRelated,
60
+ Tools::ParameterSearch,
61
+ Tools::MemberSearch,
62
+ Tools::ListTopics,
63
+ Tools::GetTopic
64
+ ],
80
65
  resources: build_topic_resources(index)
81
66
  )
82
67
  @server.server_context = {index: index}
@@ -100,7 +85,7 @@ module CF
100
85
 
101
86
  landing_page = build_landing_page
102
87
  index = @index
103
- tools = TOOLS
88
+ tools = @server.tools.values
104
89
  cors_headers = CORS_HEADERS
105
90
  public_dir = PUBLIC_DIR
106
91
 
@@ -15,7 +15,7 @@
15
15
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
16
16
  </head>
17
17
  <body>
18
- <h1><img src="/logo.svg" alt="CF Logo" class="title-logo">CF::MCP <small style="font-size: 0.5em; color: #8b949e;">v<%= version %></small></h1>
18
+ <h1><img src="/favicon.svg" alt="CF Logo" class="title-logo">CF::MCP <small style="font-size: 0.5em; color: #8b949e;">v<%= version %></small></h1>
19
19
  <p>MCP (Model Context Protocol) server for the <a href="https://github.com/RandyGaul/cute_framework">Cute Framework</a>, a C/C++ 2D game framework.</p>
20
20
 
21
21
  <div class="stats">
@@ -32,8 +32,7 @@ module CF
32
32
  )
33
33
 
34
34
  def self.call(name:, server_context: {})
35
- index = server_context[:index]
36
- return error_response("Index not available") unless index
35
+ index = Index.instance
37
36
 
38
37
  item = index.find(name)
39
38
  return text_response("Not found: '#{name}'") unless item
@@ -34,8 +34,7 @@ module CF
34
34
  NAMING_TIP = "**Tip:** Cute Framework uses `cf_` prefix for functions and `CF_` prefix for types (structs/enums)."
35
35
 
36
36
  def self.call(name:, server_context: {})
37
- index = server_context[:index]
38
- return error_response("Index not available") unless index
37
+ index = Index.instance
39
38
 
40
39
  item = index.find(name)
41
40
 
@@ -32,8 +32,7 @@ module CF
32
32
  )
33
33
 
34
34
  def self.call(name:, server_context: {})
35
- index = server_context[:index]
36
- return error_response("Index not available") unless index
35
+ index = Index.instance
37
36
 
38
37
  topic = index.find(name)
39
38
 
@@ -18,7 +18,7 @@ module CF
18
18
  input_schema(
19
19
  type: "object",
20
20
  properties: {
21
- category: {type: "string", description: "Category name (e.g., 'app', 'sprite', 'graphics'). Leave empty to list all categories."},
21
+ category: {type: "string", enum: Index.instance.categories, description: "Category name. Leave empty to list all categories."},
22
22
  type: {type: "string", enum: ["function", "struct", "enum"], description: "Optional: filter by item type"}
23
23
  }
24
24
  )
@@ -32,8 +32,7 @@ module CF
32
32
  )
33
33
 
34
34
  def self.call(category: nil, type: nil, server_context: {})
35
- index = server_context[:index]
36
- return error_response("Index not available") unless index
35
+ index = Index.instance
37
36
 
38
37
  if category.nil? || category.empty?
39
38
  # List all categories with counts by type
@@ -32,8 +32,7 @@ module CF
32
32
  )
33
33
 
34
34
  def self.call(category: nil, ordered: false, server_context: {})
35
- index = server_context[:index]
36
- return error_response("Index not available") unless index
35
+ index = Index.instance
37
36
 
38
37
  topics = ordered ? index.topics_ordered : index.topics
39
38
 
@@ -33,8 +33,7 @@ module CF
33
33
  )
34
34
 
35
35
  def self.call(query:, limit: 20, server_context: {})
36
- index = server_context[:index]
37
- return error_response("Index not available") unless index
36
+ index = Index.instance
38
37
 
39
38
  pattern = Regexp.new(Regexp.escape(query), Regexp::IGNORECASE)
40
39
  results = []
@@ -37,8 +37,7 @@ module CF
37
37
  )
38
38
 
39
39
  def self.call(type:, direction: "both", server_context: {})
40
- index = server_context[:index]
41
- return error_response("Index not available") unless index
40
+ index = Index.instance
42
41
 
43
42
  pattern = Regexp.new(Regexp.escape(type), Regexp::IGNORECASE)
44
43
  input_matches = []
@@ -22,7 +22,7 @@ module CF
22
22
  properties: {
23
23
  query: {type: "string", description: "Search query (searches in name, description, and remarks)"},
24
24
  type: {type: "string", enum: ["function", "struct", "enum", "topic"], description: "Optional: filter by item type"},
25
- category: {type: "string", description: "Optional: filter by category (e.g., 'app', 'sprite', 'graphics')"},
25
+ category: {type: "string", enum: Index.instance.categories, description: "Optional: filter by category"},
26
26
  limit: {type: "integer", description: "Maximum number of results to return (default: 20)"}
27
27
  },
28
28
  required: ["query"]
@@ -36,21 +36,40 @@ module CF
36
36
  open_world_hint: false
37
37
  )
38
38
 
39
- DETAILS_TIP = "**Tip:** Use `get_details` for API items or `get_topic` for topic guides to get full documentation."
39
+ DETAILS_TIPS = {
40
+ "function" => "**Tip:** Use `get_details` with an exact name to get full documentation including signature, parameters, and examples.",
41
+ "struct" => "**Tip:** Use `get_details` with an exact name to get full documentation including members and examples.",
42
+ "enum" => "**Tip:** Use `get_details` with an exact name to get full documentation including values and examples.",
43
+ "topic" => "**Tip:** Use `get_topic` with an exact name to get the full topic guide.",
44
+ nil => "**Tip:** Use `get_details` for API items or `get_topic` for topic guides to get full documentation."
45
+ }.freeze
46
+
47
+ TYPE_LABELS = {
48
+ "function" => "function(s)",
49
+ "struct" => "struct(s)",
50
+ "enum" => "enum(s)",
51
+ "topic" => "topic(s)",
52
+ nil => "result(s)"
53
+ }.freeze
40
54
 
41
55
  def self.call(query:, type: nil, category: nil, limit: 20, server_context: {})
42
- index = server_context[:index]
43
- return error_response("Index not available") unless index
56
+ index = Index.instance
44
57
 
45
58
  results = index.search(query, type: type, category: category, limit: limit)
46
59
 
60
+ filter_suggestion = if type
61
+ "To find more results, narrow your search with a `category` filter."
62
+ else
63
+ "To find more results, narrow your search with `type` or `category` filters."
64
+ end
65
+
47
66
  text = format_search_results(
48
67
  results,
49
68
  query: query,
50
- type_label: "result(s)",
69
+ type_label: TYPE_LABELS[type],
51
70
  limit: limit,
52
- details_tip: DETAILS_TIP,
53
- filter_suggestion: "To find more results, narrow your search with `type` or `category` filters."
71
+ details_tip: DETAILS_TIPS[type],
72
+ filter_suggestion: filter_suggestion
54
73
  )
55
74
  text_response(text)
56
75
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module CF
4
4
  module MCP
5
- VERSION = "0.15.5"
5
+ VERSION = "0.16.2"
6
6
  end
7
7
  end
data/lib/cf/mcp.rb CHANGED
@@ -20,5 +20,16 @@ module CF
20
20
  def self.root
21
21
  @root ||= Pathname.new(File.expand_path("../..", __dir__))
22
22
  end
23
+
24
+ module Tools
25
+ autoload :SearchTool, "cf/mcp/tools/search_tool"
26
+ autoload :ListCategory, "cf/mcp/tools/list_category"
27
+ autoload :GetDetails, "cf/mcp/tools/get_details"
28
+ autoload :FindRelated, "cf/mcp/tools/find_related"
29
+ autoload :ParameterSearch, "cf/mcp/tools/parameter_search"
30
+ autoload :MemberSearch, "cf/mcp/tools/member_search"
31
+ autoload :ListTopics, "cf/mcp/tools/list_topics"
32
+ autoload :GetTopic, "cf/mcp/tools/get_topic"
33
+ end
23
34
  end
24
35
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cf-mcp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.5
4
+ version: 0.16.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Usewicz
@@ -125,10 +125,7 @@ files:
125
125
  - lib/cf/mcp/tools/member_search.rb
126
126
  - lib/cf/mcp/tools/parameter_search.rb
127
127
  - lib/cf/mcp/tools/response_helpers.rb
128
- - lib/cf/mcp/tools/search_enums.rb
129
- - lib/cf/mcp/tools/search_functions.rb
130
128
  - lib/cf/mcp/tools/search_result_formatter.rb
131
- - lib/cf/mcp/tools/search_structs.rb
132
129
  - lib/cf/mcp/tools/search_tool.rb
133
130
  - lib/cf/mcp/topic_parser.rb
134
131
  - lib/cf/mcp/version.rb
@@ -1,59 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "mcp"
4
- require_relative "response_helpers"
5
- require_relative "search_result_formatter"
6
-
7
- module CF
8
- module MCP
9
- module Tools
10
- class SearchEnums < ::MCP::Tool
11
- extend ResponseHelpers
12
- extend SearchResultFormatter
13
-
14
- TITLE = "Search Enums"
15
-
16
- tool_name "search_enums"
17
- title TITLE
18
- description "Search Cute Framework enums"
19
-
20
- input_schema(
21
- type: "object",
22
- properties: {
23
- query: {type: "string", description: "Search query"},
24
- category: {type: "string", description: "Optional: filter by category"},
25
- limit: {type: "integer", description: "Maximum results (default: 20)"}
26
- },
27
- required: ["query"]
28
- )
29
-
30
- annotations(
31
- title: TITLE,
32
- read_only_hint: true,
33
- destructive_hint: false,
34
- idempotent_hint: true,
35
- open_world_hint: false
36
- )
37
-
38
- DETAILS_TIP = "**Tip:** Use `get_details` with an exact name to get full documentation including values and examples."
39
-
40
- def self.call(query:, category: nil, limit: 20, server_context: {})
41
- index = server_context[:index]
42
- return error_response("Index not available") unless index
43
-
44
- results = index.search(query, type: :enum, category: category, limit: limit)
45
-
46
- text = format_search_results(
47
- results,
48
- query: query,
49
- type_label: "enum(s)",
50
- limit: limit,
51
- details_tip: DETAILS_TIP,
52
- filter_suggestion: "To find more results, narrow your search with a `category` filter."
53
- )
54
- text_response(text)
55
- end
56
- end
57
- end
58
- end
59
- end
@@ -1,59 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "mcp"
4
- require_relative "response_helpers"
5
- require_relative "search_result_formatter"
6
-
7
- module CF
8
- module MCP
9
- module Tools
10
- class SearchFunctions < ::MCP::Tool
11
- extend ResponseHelpers
12
- extend SearchResultFormatter
13
-
14
- TITLE = "Search Functions"
15
-
16
- tool_name "search_functions"
17
- title TITLE
18
- description "Search Cute Framework functions"
19
-
20
- input_schema(
21
- type: "object",
22
- properties: {
23
- query: {type: "string", description: "Search query"},
24
- category: {type: "string", description: "Optional: filter by category"},
25
- limit: {type: "integer", description: "Maximum results (default: 20)"}
26
- },
27
- required: ["query"]
28
- )
29
-
30
- annotations(
31
- title: TITLE,
32
- read_only_hint: true,
33
- destructive_hint: false,
34
- idempotent_hint: true,
35
- open_world_hint: false
36
- )
37
-
38
- DETAILS_TIP = "**Tip:** Use `get_details` with an exact name to get full documentation including signature, parameters, and examples."
39
-
40
- def self.call(query:, category: nil, limit: 20, server_context: {})
41
- index = server_context[:index]
42
- return error_response("Index not available") unless index
43
-
44
- results = index.search(query, type: :function, category: category, limit: limit)
45
-
46
- text = format_search_results(
47
- results,
48
- query: query,
49
- type_label: "function(s)",
50
- limit: limit,
51
- details_tip: DETAILS_TIP,
52
- filter_suggestion: "To find more results, narrow your search with a `category` filter."
53
- )
54
- text_response(text)
55
- end
56
- end
57
- end
58
- end
59
- end
@@ -1,59 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "mcp"
4
- require_relative "response_helpers"
5
- require_relative "search_result_formatter"
6
-
7
- module CF
8
- module MCP
9
- module Tools
10
- class SearchStructs < ::MCP::Tool
11
- extend ResponseHelpers
12
- extend SearchResultFormatter
13
-
14
- TITLE = "Search Structs"
15
-
16
- tool_name "search_structs"
17
- title TITLE
18
- description "Search Cute Framework structs"
19
-
20
- input_schema(
21
- type: "object",
22
- properties: {
23
- query: {type: "string", description: "Search query"},
24
- category: {type: "string", description: "Optional: filter by category"},
25
- limit: {type: "integer", description: "Maximum results (default: 20)"}
26
- },
27
- required: ["query"]
28
- )
29
-
30
- annotations(
31
- title: TITLE,
32
- read_only_hint: true,
33
- destructive_hint: false,
34
- idempotent_hint: true,
35
- open_world_hint: false
36
- )
37
-
38
- DETAILS_TIP = "**Tip:** Use `get_details` with an exact name to get full documentation including members and examples."
39
-
40
- def self.call(query:, category: nil, limit: 20, server_context: {})
41
- index = server_context[:index]
42
- return error_response("Index not available") unless index
43
-
44
- results = index.search(query, type: :struct, category: category, limit: limit)
45
-
46
- text = format_search_results(
47
- results,
48
- query: query,
49
- type_label: "struct(s)",
50
- limit: limit,
51
- details_tip: DETAILS_TIP,
52
- filter_suggestion: "To find more results, narrow your search with a `category` filter."
53
- )
54
- text_response(text)
55
- end
56
- end
57
- end
58
- end
59
- end