cf-mcp 0.9.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.
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "models/topic_doc"
4
+
5
+ module CF
6
+ module MCP
7
+ class TopicParser
8
+ # Pattern to match markdown links: [text](../category/name.md)
9
+ API_LINK_PATTERN = %r{\[`?([^\]]+)`?\]\(\.\./(\w+)/(\w+)\.md\)}
10
+
11
+ # Pattern to match topic links: [text](./topic_name.md) or [text](../topics/topic_name.md)
12
+ TOPIC_LINK_PATTERN = %r{\[([^\]]+)\]\((?:\./|\.\./topics/)(\w+)\.md\)}
13
+
14
+ # Pattern to match section headings
15
+ SECTION_PATTERN = /^##\s+(.+)$/
16
+
17
+ def parse_file(path)
18
+ content = File.read(path)
19
+ filename = File.basename(path, ".md")
20
+
21
+ return nil if filename == "index"
22
+
23
+ parse_topic(content, filename, File.basename(path))
24
+ end
25
+
26
+ def parse_directory(path)
27
+ topics = []
28
+ reading_order = parse_reading_order(File.join(path, "index.md"))
29
+
30
+ Dir.glob(File.join(path, "*.md")).each do |topic_file|
31
+ next if File.basename(topic_file) == "index.md"
32
+
33
+ topic = parse_file(topic_file)
34
+ if topic
35
+ topic.reading_order = reading_order[topic.name]
36
+ topics << topic
37
+ end
38
+ end
39
+
40
+ topics
41
+ end
42
+
43
+ def parse_reading_order(index_path)
44
+ return {} unless File.exist?(index_path)
45
+
46
+ content = File.read(index_path)
47
+ order = {}
48
+ position = 0
49
+
50
+ # Match numbered list items with topic links
51
+ content.scan(/^\d+\.\s+\[([^\]]+)\]\(\.\/(\w+)\.md\)/) do |_title, slug|
52
+ order[slug] = position
53
+ position += 1
54
+ end
55
+
56
+ order
57
+ end
58
+
59
+ private
60
+
61
+ def parse_topic(content, slug, source_file)
62
+ extract_title(content)
63
+ brief = extract_brief(content)
64
+ sections = extract_sections(content)
65
+
66
+ func_refs, struct_refs, enum_refs = extract_api_references(content)
67
+ topic_refs = extract_topic_references(content)
68
+
69
+ category = derive_category(slug)
70
+
71
+ Models::TopicDoc.new(
72
+ name: slug,
73
+ brief: brief,
74
+ category: category,
75
+ content: content,
76
+ sections: sections,
77
+ function_references: func_refs.uniq,
78
+ struct_references: struct_refs.uniq,
79
+ enum_references: enum_refs.uniq,
80
+ topic_references: topic_refs.uniq,
81
+ source_file: source_file
82
+ )
83
+ end
84
+
85
+ def extract_title(content)
86
+ content.lines.find { |line| line.start_with?("# ") }&.sub(/^#\s+/, "")&.strip
87
+ end
88
+
89
+ def extract_brief(content)
90
+ lines = content.lines
91
+ in_paragraph = false
92
+ paragraph_lines = []
93
+
94
+ lines.each do |line|
95
+ next if line.start_with?("#")
96
+
97
+ if line.strip.empty?
98
+ break if in_paragraph
99
+ next
100
+ end
101
+
102
+ in_paragraph = true
103
+ paragraph_lines << line.strip
104
+ end
105
+
106
+ # Strip markdown links but keep the text
107
+ paragraph_lines.join(" ").gsub(/\[([^\]]+)\]\([^)]+\)/, '\1')
108
+ end
109
+
110
+ def extract_sections(content)
111
+ sections = []
112
+ current_title = nil
113
+ current_content = []
114
+
115
+ content.lines.each do |line|
116
+ if line =~ SECTION_PATTERN
117
+ if current_title
118
+ sections << Models::TopicDoc::Section.new(
119
+ title: current_title,
120
+ content: current_content.join
121
+ )
122
+ end
123
+ current_title = ::Regexp.last_match(1).strip
124
+ current_content = []
125
+ elsif current_title
126
+ current_content << line
127
+ end
128
+ end
129
+
130
+ # Add last section
131
+ if current_title
132
+ sections << Models::TopicDoc::Section.new(
133
+ title: current_title,
134
+ content: current_content.join
135
+ )
136
+ end
137
+
138
+ sections
139
+ end
140
+
141
+ def extract_api_references(content)
142
+ func_refs = []
143
+ struct_refs = []
144
+ enum_refs = []
145
+
146
+ content.scan(API_LINK_PATTERN) do |_text, _category, name|
147
+ if name.start_with?("cf_")
148
+ func_refs << name
149
+ elsif name.start_with?("CF_") || name.match?(/^[A-Z]/)
150
+ # Uppercase names are likely structs or enums
151
+ # Will be refined when cross-referenced with index
152
+ struct_refs << name
153
+ end
154
+ end
155
+
156
+ [func_refs, struct_refs, enum_refs]
157
+ end
158
+
159
+ def extract_topic_references(content)
160
+ refs = []
161
+ content.scan(TOPIC_LINK_PATTERN) do |_text, slug|
162
+ refs << slug
163
+ end
164
+ refs
165
+ end
166
+
167
+ def derive_category(slug)
168
+ CATEGORY_MAP[slug] || slug
169
+ end
170
+
171
+ CATEGORY_MAP = {
172
+ "audio" => "audio",
173
+ "camera" => "draw",
174
+ "collision" => "collision",
175
+ "coroutines" => "coroutine",
176
+ "drawing" => "draw",
177
+ "input" => "input",
178
+ "networking" => "net",
179
+ "strings" => "string",
180
+ "random_numbers" => "math",
181
+ "application_window" => "app",
182
+ "game_loop_and_time" => "time",
183
+ "file_io" => "file",
184
+ "virtual_file_system" => "file",
185
+ "multithreading" => "thread",
186
+ "atomics" => "atomic",
187
+ "data_structures" => "array",
188
+ "allocator" => "alloc",
189
+ "emscripten" => "app",
190
+ "ios" => "app",
191
+ "web" => "https",
192
+ "dear_imgui" => "imgui",
193
+ "low_level_graphics" => "graphics",
194
+ "renderer" => "graphics",
195
+ "shader_compilation" => "graphics"
196
+ }.freeze
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CF
4
+ module MCP
5
+ VERSION = "0.9.2"
6
+ end
7
+ end
data/lib/cf/mcp.rb ADDED
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
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/server"
12
+ require_relative "mcp/downloader"
13
+ require_relative "mcp/cli"
14
+
15
+ module CF
16
+ module MCP
17
+ class Error < StandardError; end
18
+
19
+ def self.root
20
+ @root ||= Pathname.new(File.expand_path("../..", __dir__))
21
+ end
22
+ end
23
+ end
data/sig/cf/mcp.rbs ADDED
@@ -0,0 +1,84 @@
1
+ module CF
2
+ module MCP
3
+ VERSION: String
4
+
5
+ class Error < StandardError
6
+ end
7
+
8
+ class Parser
9
+ def parse_file: (String path) -> Array[Models::DocItem]
10
+ def parse_directory: (String path) -> Array[Models::DocItem]
11
+ end
12
+
13
+ class Index
14
+ attr_reader items: Hash[String, Models::DocItem]
15
+ attr_reader by_type: Hash[Symbol, Array[Models::DocItem]]
16
+ attr_reader by_category: Hash[String, Array[Models::DocItem]]
17
+
18
+ def add: (Models::DocItem item) -> void
19
+ def find: (String name) -> Models::DocItem?
20
+ def search: (String query, ?type: Symbol?, ?category: String?, ?limit: Integer) -> Array[Models::DocItem]
21
+ def functions: () -> Array[Models::DocItem]
22
+ def structs: () -> Array[Models::DocItem]
23
+ def enums: () -> Array[Models::DocItem]
24
+ def categories: () -> Array[String]
25
+ def items_in_category: (String category) -> Array[Models::DocItem]
26
+ def size: () -> Integer
27
+ def stats: () -> Hash[Symbol, Integer]
28
+ end
29
+
30
+ class CLI
31
+ def initialize: (Array[String] args) -> void
32
+ def run: () -> void
33
+ end
34
+
35
+ class Server
36
+ attr_reader server: untyped
37
+ attr_reader index: Index
38
+
39
+ def initialize: (Index index) -> void
40
+ def run_stdio: () -> void
41
+ def run_http: (?port: Integer) -> void
42
+ def run_sse: (?port: Integer) -> void
43
+ end
44
+
45
+ module Models
46
+ class DocItem
47
+ attr_accessor name: String?
48
+ attr_accessor type: Symbol?
49
+ attr_accessor category: String?
50
+ attr_accessor brief: String?
51
+ attr_accessor remarks: String?
52
+ attr_accessor example: String?
53
+ attr_accessor example_brief: String?
54
+ attr_accessor related: Array[String]
55
+ attr_accessor source_file: String?
56
+
57
+ def matches?: (String query) -> bool
58
+ def to_h: () -> Hash[Symbol, untyped]
59
+ def to_summary: () -> String
60
+ def to_text: (?detailed: bool) -> String
61
+ end
62
+
63
+ class FunctionDoc < DocItem
64
+ attr_accessor signature: String?
65
+ attr_accessor parameters: Array[Parameter]
66
+ attr_accessor return_value: String?
67
+
68
+ Parameter: untyped
69
+ end
70
+
71
+ class StructDoc < DocItem
72
+ attr_accessor members: Array[Member]
73
+
74
+ Member: untyped
75
+ end
76
+
77
+ class EnumDoc < DocItem
78
+ attr_accessor entries: Array[Entry]
79
+
80
+ Entry: untyped
81
+ end
82
+ end
83
+ end
84
+ end
metadata ADDED
@@ -0,0 +1,150 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cf-mcp
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.2
5
+ platform: ruby
6
+ authors:
7
+ - Piotr Usewicz
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: mcp
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '0.5'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '0.5'
26
+ - !ruby/object:Gem::Dependency
27
+ name: puma
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '6.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '6.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rack
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '3.0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '3.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rackup
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '2.0'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '2.0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rubyzip
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '2.3'
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '2.3'
82
+ description: An MCP server that indexes Cute Framework header files and provides search
83
+ functionality for structs, functions, enums, and other elements.
84
+ email:
85
+ - piotr@layer22.com
86
+ executables:
87
+ - cf-mcp
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - LICENSE.txt
92
+ - Manifest.txt
93
+ - README.md
94
+ - Rakefile
95
+ - config.ru
96
+ - exe/cf-mcp
97
+ - lib/cf/mcp.rb
98
+ - lib/cf/mcp/cli.rb
99
+ - lib/cf/mcp/downloader.rb
100
+ - lib/cf/mcp/index.rb
101
+ - lib/cf/mcp/models/doc_item.rb
102
+ - lib/cf/mcp/models/enum_doc.rb
103
+ - lib/cf/mcp/models/function_doc.rb
104
+ - lib/cf/mcp/models/struct_doc.rb
105
+ - lib/cf/mcp/models/topic_doc.rb
106
+ - lib/cf/mcp/parser.rb
107
+ - lib/cf/mcp/server.rb
108
+ - lib/cf/mcp/templates/index.erb
109
+ - lib/cf/mcp/templates/script.js
110
+ - lib/cf/mcp/templates/style.css
111
+ - lib/cf/mcp/tools/find_related.rb
112
+ - lib/cf/mcp/tools/get_details.rb
113
+ - lib/cf/mcp/tools/get_topic.rb
114
+ - lib/cf/mcp/tools/list_category.rb
115
+ - lib/cf/mcp/tools/list_topics.rb
116
+ - lib/cf/mcp/tools/member_search.rb
117
+ - lib/cf/mcp/tools/parameter_search.rb
118
+ - lib/cf/mcp/tools/search_enums.rb
119
+ - lib/cf/mcp/tools/search_functions.rb
120
+ - lib/cf/mcp/tools/search_structs.rb
121
+ - lib/cf/mcp/tools/search_tool.rb
122
+ - lib/cf/mcp/topic_parser.rb
123
+ - lib/cf/mcp/version.rb
124
+ - sig/cf/mcp.rbs
125
+ homepage: https://github.com/pusewicz/cf-mcp
126
+ licenses:
127
+ - MIT
128
+ metadata:
129
+ allowed_push_host: https://rubygems.org
130
+ homepage_uri: https://github.com/pusewicz/cf-mcp
131
+ source_code_uri: https://github.com/pusewicz/cf-mcp
132
+ changelog_uri: https://github.com/pusewicz/cf-mcp/blob/main/CHANGELOG.md
133
+ rdoc_options: []
134
+ require_paths:
135
+ - lib
136
+ required_ruby_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: 3.2.0
141
+ required_rubygems_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ requirements: []
147
+ rubygems_version: 4.0.3
148
+ specification_version: 4
149
+ summary: MCP server providing documentation tools for Cute Framework
150
+ test_files: []