cf-mcp 0.16.2 → 0.17.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: 8616f673c57b67cd09602a2b49611c030fc058d63f1701dd2cec2cff2c04d4ad
4
- data.tar.gz: abc2b55c27a9f4ace220d5fd9d013cb5944c02d8bd6f43106be141ac05aaa208
3
+ metadata.gz: d62d42b607e127916235fb3bbd2a2dace73fc1aefc9216dc1d9f92acfa6c423a
4
+ data.tar.gz: 12a75fda9901e78aee0fc6f7c3ed448ce52f6130d868f036aa674464efea640c
5
5
  SHA512:
6
- metadata.gz: 7587ed21fd3fe5e3f81a7fd574cc32afc3da7c69b7c9869241508ad8a3f477ec813297f1d893b0dba5c3c9ccc4411773adacce35eeb8e15a936328f613d55186
7
- data.tar.gz: 02e5f832b1c52dc25ec415d362585e7f104a526a24e7454af38192f48d60abc209c7f91175b60dd5759b0dc1670508fdf16ad61fa7db4aebe25edf7f406d984a
6
+ metadata.gz: 63eabad61e8ea796f628e8f6248ac3e147b8486ac7495ab897dbd596d13044bd6495032dabede788a25a9426fcb9b855e5ced42a541b47aaeb269e161d958887
7
+ data.tar.gz: a262522cae34cbcb9b2f9c54252709b86fa44efbf2f32f8426937784cc35d5aaf048c9267ca4e9ee53bf1b3543f5df0e4d8ca76580a881cc94b0d4ab44f355a5
data/CHANGELOG.md CHANGED
@@ -5,6 +5,22 @@ 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
+ ## [Unreleased]
9
+
10
+ ## [0.17.0] - 2026-02-09
11
+
12
+ ### Added
13
+
14
+ - SHA-based caching for downloaded Cute Framework headers - checks GitHub API for latest commit SHA before downloading to avoid redundant fetches
15
+ - GitHub API client with optional GITHUB_TOKEN support for higher rate limits (5000/hr vs 60/hr unauthenticated)
16
+ - Metadata tracking (.cf-mcp-sha file) stores downloaded version SHA for cache validation
17
+
18
+ ### Changed
19
+
20
+ - Downloader uses commit-specific archive URLs (e.g., /archive/abc1234.zip) instead of always downloading master branch
21
+ - Download process now checks for updates by comparing stored SHA with latest GitHub commit
22
+ - Refactored all internal `require_relative` statements to use `autoload` for lazy loading, improving load time and memory usage
23
+
8
24
  ## [0.16.2] - 2026-01-31
9
25
 
10
26
  ### Fixed
@@ -261,6 +277,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
261
277
  - `cf_list_category` - List items by category
262
278
  - `cf_get_details` - Get full documentation by name
263
279
 
280
+ [0.17.0]: https://github.com/pusewicz/cf-mcp/compare/v0.16.2...v0.17.0
264
281
  [0.16.2]: https://github.com/pusewicz/cf-mcp/compare/v0.16.1...v0.16.2
265
282
  [0.16.1]: https://github.com/pusewicz/cf-mcp/compare/v0.16.0...v0.16.1
266
283
  [0.16.0]: https://github.com/pusewicz/cf-mcp/compare/v0.15.5...v0.16.0
data/Manifest.txt CHANGED
@@ -8,6 +8,7 @@ exe/cf-mcp
8
8
  lib/cf/mcp.rb
9
9
  lib/cf/mcp/cli.rb
10
10
  lib/cf/mcp/downloader.rb
11
+ lib/cf/mcp/github_client.rb
11
12
  lib/cf/mcp/index.rb
12
13
  lib/cf/mcp/index_builder.rb
13
14
  lib/cf/mcp/models/doc_item.rb
data/lib/cf/mcp/cli.rb CHANGED
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "optparse"
4
- require_relative "index_builder"
5
- require_relative "server"
6
4
 
7
5
  module CF
8
6
  module MCP
@@ -4,12 +4,13 @@ require "net/http"
4
4
  require "uri"
5
5
  require "fileutils"
6
6
  require "zip"
7
- require_relative "version"
8
7
 
9
8
  module CF
10
9
  module MCP
11
10
  class Downloader
12
11
  CUTE_FRAMEWORK_ZIP_URL = "https://github.com/RandyGaul/cute_framework/archive/refs/heads/master.zip"
12
+ GITHUB_ARCHIVE_URL_TEMPLATE = "https://github.com/RandyGaul/cute_framework/archive/%{ref}.zip"
13
+ SHA_METADATA_FILE = ".cf-mcp-sha"
13
14
  DEFAULT_DOWNLOAD_DIR = File.join(Dir.tmpdir, "cf-mcp-#{VERSION}")
14
15
 
15
16
  class DownloadError < StandardError; end
@@ -24,23 +25,41 @@ module CF
24
25
  zip_path = File.join(@download_dir, "cute_framework.zip")
25
26
  base_path = File.join(@download_dir, "cute_framework")
26
27
  include_path = File.join(base_path, "include")
27
- File.join(base_path, "docs", "topics")
28
+ sha_file = File.join(@download_dir, SHA_METADATA_FILE)
28
29
 
29
- # Return existing path if already downloaded
30
- if File.directory?(include_path) && !Dir.empty?(include_path)
31
- return include_path
30
+ # Check if cache is valid
31
+ stored_sha = read_sha_metadata(sha_file)
32
+ latest_sha = fetch_latest_sha
33
+
34
+ if stored_sha && latest_sha && stored_sha == latest_sha
35
+ if File.directory?(include_path) && !Dir.empty?(include_path)
36
+ warn "Using cached Cute Framework headers (SHA: #{stored_sha})"
37
+ return include_path
38
+ end
39
+ end
40
+
41
+ # Determine download URL
42
+ if latest_sha
43
+ download_url = format(GITHUB_ARCHIVE_URL_TEMPLATE, ref: latest_sha)
44
+ warn "Downloading Cute Framework at SHA #{latest_sha}..."
45
+ else
46
+ download_url = CUTE_FRAMEWORK_ZIP_URL
47
+ warn "Downloading Cute Framework from master branch..."
32
48
  end
33
49
 
34
- download_zip(zip_path)
50
+ download_zip(zip_path, download_url)
35
51
  extract_directories(zip_path, base_path)
36
52
 
53
+ # Store metadata for future cache checks
54
+ write_sha_metadata(sha_file, latest_sha) if latest_sha
55
+
37
56
  include_path
38
57
  end
39
58
 
40
59
  private
41
60
 
42
- def download_zip(destination)
43
- uri = URI.parse(CUTE_FRAMEWORK_ZIP_URL)
61
+ def download_zip(destination, url = CUTE_FRAMEWORK_ZIP_URL)
62
+ uri = URI.parse(url)
44
63
 
45
64
  Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
46
65
  request = Net::HTTP::Get.new(uri)
@@ -73,9 +92,9 @@ module CF
73
92
  top_level_prefix = nil
74
93
 
75
94
  zip_file.each do |entry|
76
- # Find the top-level directory prefix (e.g., "cute_framework-master/")
77
- if top_level_prefix.nil? && entry.name.match?(%r{^[^/]+/include/})
78
- top_level_prefix = entry.name.match(%r{^([^/]+/)})[1]
95
+ # Find the top-level directory prefix (e.g., "cute_framework-master/" or "cute_framework-abc1234/")
96
+ if top_level_prefix.nil? && entry.name.match?(%r{^cute_framework-[^/]+/include/})
97
+ top_level_prefix = entry.name.match(%r{^(cute_framework-[^/]+/)})[1]
79
98
  break
80
99
  end
81
100
  end
@@ -107,6 +126,29 @@ module CF
107
126
  end
108
127
  end
109
128
  end
129
+
130
+ def fetch_latest_sha
131
+ client = GitHubClient.new
132
+ client.latest_commit_sha
133
+ rescue => e
134
+ warn "GitHub API error: #{e.message}"
135
+ nil
136
+ end
137
+
138
+ def read_sha_metadata(file)
139
+ return nil unless File.exist?(file)
140
+ File.read(file).strip
141
+ rescue => e
142
+ warn "Error reading SHA metadata: #{e.message}"
143
+ nil
144
+ end
145
+
146
+ def write_sha_metadata(file, sha)
147
+ return unless sha
148
+ File.write(file, sha)
149
+ rescue => e
150
+ warn "Error writing SHA metadata: #{e.message}"
151
+ end
110
152
  end
111
153
  end
112
154
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "json"
5
+ require "uri"
6
+
7
+ module CF
8
+ module MCP
9
+ class GitHubClient
10
+ GITHUB_API_BASE = "https://api.github.com"
11
+ REPO_OWNER = "RandyGaul"
12
+ REPO_NAME = "cute_framework"
13
+ DEFAULT_BRANCH = "master"
14
+
15
+ def initialize(token: ENV["GITHUB_TOKEN"])
16
+ @token = token
17
+ end
18
+
19
+ # Returns latest commit SHA (short 7-char format) or nil on failure
20
+ def latest_commit_sha
21
+ uri = URI.parse("#{GITHUB_API_BASE}/repos/#{REPO_OWNER}/#{REPO_NAME}/commits/#{DEFAULT_BRANCH}")
22
+
23
+ request = Net::HTTP::Get.new(uri)
24
+ request["Accept"] = "application/vnd.github+json"
25
+ request["Authorization"] = "Bearer #{@token}" if @token
26
+ request["User-Agent"] = "cf-mcp/#{CF::MCP::VERSION}"
27
+
28
+ response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
29
+ http.request(request)
30
+ end
31
+
32
+ return nil unless response.is_a?(Net::HTTPSuccess)
33
+
34
+ data = JSON.parse(response.body)
35
+ data.dig("sha")&.slice(0, 7) # Return short SHA
36
+ rescue
37
+ # Return nil on any error (network, JSON parse, etc.)
38
+ nil
39
+ end
40
+ end
41
+ end
42
+ end
@@ -1,10 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "parser"
4
- require_relative "topic_parser"
5
- require_relative "index"
6
- require_relative "downloader"
7
-
8
3
  module CF
9
4
  module MCP
10
5
  class IndexBuilder
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "doc_item"
4
-
5
3
  module CF
6
4
  module MCP
7
5
  module Models
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "doc_item"
4
-
5
3
  module CF
6
4
  module MCP
7
5
  module Models
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "doc_item"
4
-
5
3
  module CF
6
4
  module MCP
7
5
  module Models
data/lib/cf/mcp/parser.rb CHANGED
@@ -1,9 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "models/function_doc"
4
- require_relative "models/struct_doc"
5
- require_relative "models/enum_doc"
6
-
7
3
  module CF
8
4
  module MCP
9
5
  class Parser
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "models/topic_doc"
4
-
5
3
  module CF
6
4
  module MCP
7
5
  class TopicParser
@@ -2,6 +2,6 @@
2
2
 
3
3
  module CF
4
4
  module MCP
5
- VERSION = "0.16.2"
5
+ VERSION = "0.17.0"
6
6
  end
7
7
  end
data/lib/cf/mcp.rb CHANGED
@@ -2,25 +2,32 @@
2
2
 
3
3
  require "pathname"
4
4
  require_relative "mcp/version"
5
- require_relative "mcp/models/doc_item"
6
- require_relative "mcp/models/function_doc"
7
- require_relative "mcp/models/struct_doc"
8
- require_relative "mcp/models/enum_doc"
9
- require_relative "mcp/parser"
10
- require_relative "mcp/index"
11
- require_relative "mcp/index_builder"
12
- require_relative "mcp/server"
13
- require_relative "mcp/downloader"
14
- require_relative "mcp/cli"
15
5
 
16
6
  module CF
17
7
  module MCP
18
8
  class Error < StandardError; end
19
9
 
10
+ autoload :Parser, "cf/mcp/parser"
11
+ autoload :Index, "cf/mcp/index"
12
+ autoload :IndexBuilder, "cf/mcp/index_builder"
13
+ autoload :TopicParser, "cf/mcp/topic_parser"
14
+ autoload :Server, "cf/mcp/server"
15
+ autoload :Downloader, "cf/mcp/downloader"
16
+ autoload :GitHubClient, "cf/mcp/github_client"
17
+ autoload :CLI, "cf/mcp/cli"
18
+
20
19
  def self.root
21
20
  @root ||= Pathname.new(File.expand_path("../..", __dir__))
22
21
  end
23
22
 
23
+ module Models
24
+ autoload :DocItem, "cf/mcp/models/doc_item"
25
+ autoload :FunctionDoc, "cf/mcp/models/function_doc"
26
+ autoload :StructDoc, "cf/mcp/models/struct_doc"
27
+ autoload :EnumDoc, "cf/mcp/models/enum_doc"
28
+ autoload :TopicDoc, "cf/mcp/models/topic_doc"
29
+ end
30
+
24
31
  module Tools
25
32
  autoload :SearchTool, "cf/mcp/tools/search_tool"
26
33
  autoload :ListCategory, "cf/mcp/tools/list_category"
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.16.2
4
+ version: 0.17.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Usewicz
@@ -98,6 +98,7 @@ files:
98
98
  - lib/cf/mcp.rb
99
99
  - lib/cf/mcp/cli.rb
100
100
  - lib/cf/mcp/downloader.rb
101
+ - lib/cf/mcp/github_client.rb
101
102
  - lib/cf/mcp/index.rb
102
103
  - lib/cf/mcp/index_builder.rb
103
104
  - lib/cf/mcp/models/doc_item.rb
@@ -152,7 +153,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
152
153
  - !ruby/object:Gem::Version
153
154
  version: '0'
154
155
  requirements: []
155
- rubygems_version: 4.0.3
156
+ rubygems_version: 4.0.6
156
157
  specification_version: 4
157
158
  summary: MCP server providing documentation tools for Cute Framework
158
159
  test_files: []