cf-mcp 0.12.0 → 0.12.1
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 +4 -4
- data/CHANGELOG.md +10 -0
- data/Manifest.txt +3 -0
- data/lib/cf/mcp/cli.rb +8 -74
- data/lib/cf/mcp/index_builder.rb +89 -0
- data/lib/cf/mcp/models/doc_item.rb +46 -21
- data/lib/cf/mcp/models/enum_doc.rb +11 -48
- data/lib/cf/mcp/models/function_doc.rb +31 -54
- data/lib/cf/mcp/models/struct_doc.rb +11 -48
- data/lib/cf/mcp/server.rb +8 -71
- data/lib/cf/mcp/tools/find_related.rb +3 -8
- data/lib/cf/mcp/tools/get_details.rb +3 -8
- data/lib/cf/mcp/tools/get_topic.rb +3 -8
- data/lib/cf/mcp/tools/list_category.rb +3 -8
- data/lib/cf/mcp/tools/list_topics.rb +3 -8
- data/lib/cf/mcp/tools/member_search.rb +3 -8
- data/lib/cf/mcp/tools/parameter_search.rb +3 -8
- data/lib/cf/mcp/tools/response_helpers.rb +19 -0
- data/lib/cf/mcp/tools/search_enums.rb +14 -23
- data/lib/cf/mcp/tools/search_functions.rb +14 -23
- data/lib/cf/mcp/tools/search_result_formatter.rb +30 -0
- data/lib/cf/mcp/tools/search_structs.rb +14 -23
- data/lib/cf/mcp/tools/search_tool.rb +14 -23
- data/lib/cf/mcp/version.rb +1 -1
- data/lib/cf/mcp.rb +1 -0
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 30c68992a64d36c8265af4bdcd018dd116c7d2e59479ab1fd83213896028b86d
|
|
4
|
+
data.tar.gz: 4a4e5b715cdca959296e1043bf3173a2c4c314a40ab5c1cb76154dd20b823557
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1704a19c8b49fb37258b247e5689a15f7ed67a2cce9ff226ee5ac0c4689dbc45b547150d426cc91e15e09a2f36405752f58a98e5b14596e5282dc7b9784c4e15
|
|
7
|
+
data.tar.gz: d1c5c7f81871cddb2f0a1bb81b98272b7d3d07a8d7d40bfafd08c338ef2e6b8073d296407ae2dd74575b7ec2738864c1c5bd941408f9f681e373002af363a70d
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,15 @@ 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.12.1] - 2026-01-14
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Refactored tool classes to use shared `ResponseHelpers` module, removing duplicate code
|
|
13
|
+
- Extracted `IndexBuilder` class to consolidate index building logic from CLI and HTTPServer
|
|
14
|
+
- Extracted `SearchResultFormatter` module for consistent search result formatting
|
|
15
|
+
- Simplified model `to_text()` methods using template method pattern in `DocItem` base class
|
|
16
|
+
|
|
8
17
|
## [0.12.0] - 2026-01-14
|
|
9
18
|
|
|
10
19
|
### Changed
|
|
@@ -139,6 +148,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
139
148
|
- `cf_list_category` - List items by category
|
|
140
149
|
- `cf_get_details` - Get full documentation by name
|
|
141
150
|
|
|
151
|
+
[0.12.1]: https://github.com/pusewicz/cf-mcp/compare/v0.12.0...v0.12.1
|
|
142
152
|
[0.12.0]: https://github.com/pusewicz/cf-mcp/compare/v0.10.1...v0.12.0
|
|
143
153
|
[0.10.1]: https://github.com/pusewicz/cf-mcp/compare/v0.10.0...v0.10.1
|
|
144
154
|
[0.10.0]: https://github.com/pusewicz/cf-mcp/compare/v0.9.3...v0.10.0
|
data/Manifest.txt
CHANGED
|
@@ -9,6 +9,7 @@ lib/cf/mcp.rb
|
|
|
9
9
|
lib/cf/mcp/cli.rb
|
|
10
10
|
lib/cf/mcp/downloader.rb
|
|
11
11
|
lib/cf/mcp/index.rb
|
|
12
|
+
lib/cf/mcp/index_builder.rb
|
|
12
13
|
lib/cf/mcp/models/doc_item.rb
|
|
13
14
|
lib/cf/mcp/models/enum_doc.rb
|
|
14
15
|
lib/cf/mcp/models/function_doc.rb
|
|
@@ -26,8 +27,10 @@ lib/cf/mcp/tools/list_category.rb
|
|
|
26
27
|
lib/cf/mcp/tools/list_topics.rb
|
|
27
28
|
lib/cf/mcp/tools/member_search.rb
|
|
28
29
|
lib/cf/mcp/tools/parameter_search.rb
|
|
30
|
+
lib/cf/mcp/tools/response_helpers.rb
|
|
29
31
|
lib/cf/mcp/tools/search_enums.rb
|
|
30
32
|
lib/cf/mcp/tools/search_functions.rb
|
|
33
|
+
lib/cf/mcp/tools/search_result_formatter.rb
|
|
31
34
|
lib/cf/mcp/tools/search_structs.rb
|
|
32
35
|
lib/cf/mcp/tools/search_tool.rb
|
|
33
36
|
lib/cf/mcp/topic_parser.rb
|
data/lib/cf/mcp/cli.rb
CHANGED
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "optparse"
|
|
4
|
-
require_relative "
|
|
5
|
-
require_relative "topic_parser"
|
|
6
|
-
require_relative "index"
|
|
4
|
+
require_relative "index_builder"
|
|
7
5
|
require_relative "server"
|
|
8
|
-
require_relative "downloader"
|
|
9
6
|
|
|
10
7
|
module CF
|
|
11
8
|
module MCP
|
|
12
9
|
class CLI
|
|
13
|
-
DEFAULT_HEADERS_PATH = File.expand_path("~/Work/GitHub/pusewicz/cute_framework/include")
|
|
14
|
-
|
|
15
10
|
def initialize(args)
|
|
16
11
|
@args = args
|
|
17
12
|
@options = parse_args
|
|
@@ -90,17 +85,19 @@ module CF
|
|
|
90
85
|
end
|
|
91
86
|
|
|
92
87
|
def run_server(mode)
|
|
93
|
-
|
|
88
|
+
builder = IndexBuilder.new(root: @options[:root], download: @options[:download])
|
|
94
89
|
|
|
95
|
-
unless
|
|
96
|
-
warn "Error: Headers directory not found: #{headers_path}"
|
|
90
|
+
unless builder.valid?
|
|
91
|
+
warn "Error: Headers directory not found: #{builder.headers_path}"
|
|
97
92
|
warn "Use --root to specify the path to Cute Framework headers"
|
|
98
93
|
warn "Or use --download to fetch headers from GitHub"
|
|
99
94
|
exit 1
|
|
100
95
|
end
|
|
101
96
|
|
|
102
|
-
warn "Parsing headers from: #{headers_path}"
|
|
103
|
-
index =
|
|
97
|
+
warn "Parsing headers from: #{builder.headers_path}"
|
|
98
|
+
index = builder.build do |event, path, count|
|
|
99
|
+
warn "Indexed #{count} topics from: #{path}" if event == :topics_indexed
|
|
100
|
+
end
|
|
104
101
|
warn "Indexed #{index.stats[:total]} items (#{index.stats[:functions]} functions, #{index.stats[:structs]} structs, #{index.stats[:enums]} enums)"
|
|
105
102
|
|
|
106
103
|
server = Server.new(index)
|
|
@@ -123,69 +120,6 @@ module CF
|
|
|
123
120
|
warn "MCP endpoint available at http://localhost:#{port}/http"
|
|
124
121
|
Rackup::Server.start(app: app, Host: host, Port: port, Logger: $stderr)
|
|
125
122
|
end
|
|
126
|
-
|
|
127
|
-
def resolve_headers_path
|
|
128
|
-
return @options[:root] if @options[:root]
|
|
129
|
-
return ENV["CF_HEADERS_PATH"] if ENV["CF_HEADERS_PATH"]
|
|
130
|
-
|
|
131
|
-
if @options[:download]
|
|
132
|
-
warn "Downloading Cute Framework headers from GitHub..."
|
|
133
|
-
downloader = Downloader.new
|
|
134
|
-
path = downloader.download_and_extract
|
|
135
|
-
warn "Downloaded headers to: #{path}"
|
|
136
|
-
return path
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
DEFAULT_HEADERS_PATH
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
def build_index(headers_path)
|
|
143
|
-
parser = Parser.new
|
|
144
|
-
index = Index.new
|
|
145
|
-
|
|
146
|
-
parser.parse_directory(headers_path).each do |item|
|
|
147
|
-
index.add(item)
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
# Parse topics if available
|
|
151
|
-
topics_path = find_topics_path(headers_path)
|
|
152
|
-
if topics_path && File.directory?(topics_path)
|
|
153
|
-
topic_parser = TopicParser.new
|
|
154
|
-
topic_parser.parse_directory(topics_path).each do |topic|
|
|
155
|
-
refine_topic_references(topic, index)
|
|
156
|
-
index.add(topic)
|
|
157
|
-
end
|
|
158
|
-
warn "Indexed #{index.stats[:topics]} topics from: #{topics_path}"
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
index
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
def find_topics_path(headers_path)
|
|
165
|
-
# If headers_path is .../cute_framework/include, topics is at .../cute_framework/docs/topics
|
|
166
|
-
base = File.dirname(headers_path)
|
|
167
|
-
topics_path = File.join(base, "docs", "topics")
|
|
168
|
-
return topics_path if File.directory?(topics_path)
|
|
169
|
-
|
|
170
|
-
# Alternative: topics directly under headers parent
|
|
171
|
-
topics_path = File.join(base, "topics")
|
|
172
|
-
return topics_path if File.directory?(topics_path)
|
|
173
|
-
|
|
174
|
-
nil
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
def refine_topic_references(topic, index)
|
|
178
|
-
# Move items from struct_references to enum_references if they're actually enums
|
|
179
|
-
topic.struct_references.dup.each do |ref|
|
|
180
|
-
item = index.find(ref)
|
|
181
|
-
next unless item
|
|
182
|
-
|
|
183
|
-
if item.type == :enum
|
|
184
|
-
topic.struct_references.delete(ref)
|
|
185
|
-
topic.enum_references << ref unless topic.enum_references.include?(ref)
|
|
186
|
-
end
|
|
187
|
-
end
|
|
188
|
-
end
|
|
189
123
|
end
|
|
190
124
|
end
|
|
191
125
|
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "parser"
|
|
4
|
+
require_relative "topic_parser"
|
|
5
|
+
require_relative "index"
|
|
6
|
+
require_relative "downloader"
|
|
7
|
+
|
|
8
|
+
module CF
|
|
9
|
+
module MCP
|
|
10
|
+
class IndexBuilder
|
|
11
|
+
DEFAULT_HEADERS_PATH = File.expand_path("~/Work/GitHub/pusewicz/cute_framework/include")
|
|
12
|
+
|
|
13
|
+
attr_reader :headers_path
|
|
14
|
+
|
|
15
|
+
def initialize(root: nil, download: false)
|
|
16
|
+
@headers_path = resolve_headers_path(root: root, download: download)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def build
|
|
20
|
+
parser = Parser.new
|
|
21
|
+
index = Index.new
|
|
22
|
+
|
|
23
|
+
parser.parse_directory(headers_path).each do |item|
|
|
24
|
+
index.add(item)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Parse topics if available
|
|
28
|
+
topics_path = find_topics_path(headers_path)
|
|
29
|
+
if topics_path && File.directory?(topics_path)
|
|
30
|
+
topic_parser = TopicParser.new
|
|
31
|
+
topic_parser.parse_directory(topics_path).each do |topic|
|
|
32
|
+
refine_topic_references(topic, index)
|
|
33
|
+
index.add(topic)
|
|
34
|
+
end
|
|
35
|
+
yield(:topics_indexed, topics_path, index.stats[:topics]) if block_given?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
index
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def valid?
|
|
42
|
+
File.directory?(headers_path)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def resolve_headers_path(root:, download:)
|
|
48
|
+
return root if root
|
|
49
|
+
return ENV["CF_HEADERS_PATH"] if ENV["CF_HEADERS_PATH"]
|
|
50
|
+
|
|
51
|
+
if download
|
|
52
|
+
warn "Downloading Cute Framework headers from GitHub..."
|
|
53
|
+
downloader = Downloader.new
|
|
54
|
+
path = downloader.download_and_extract
|
|
55
|
+
warn "Downloaded headers to: #{path}"
|
|
56
|
+
return path
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
DEFAULT_HEADERS_PATH
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def find_topics_path(headers_path)
|
|
63
|
+
# If headers_path is .../cute_framework/include, topics is at .../cute_framework/docs/topics
|
|
64
|
+
base = File.dirname(headers_path)
|
|
65
|
+
topics_path = File.join(base, "docs", "topics")
|
|
66
|
+
return topics_path if File.directory?(topics_path)
|
|
67
|
+
|
|
68
|
+
# Alternative: topics directly under headers parent
|
|
69
|
+
topics_path = File.join(base, "topics")
|
|
70
|
+
return topics_path if File.directory?(topics_path)
|
|
71
|
+
|
|
72
|
+
nil
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def refine_topic_references(topic, index)
|
|
76
|
+
# Move items from struct_references to enum_references if they're actually enums
|
|
77
|
+
topic.struct_references.dup.each do |ref|
|
|
78
|
+
item = index.find(ref)
|
|
79
|
+
next unless item
|
|
80
|
+
|
|
81
|
+
if item.type == :enum
|
|
82
|
+
topic.struct_references.delete(ref)
|
|
83
|
+
topic.enum_references << ref unless topic.enum_references.include?(ref)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -128,6 +128,23 @@ module CF
|
|
|
128
128
|
end
|
|
129
129
|
|
|
130
130
|
def to_text(detailed: false, index: nil)
|
|
131
|
+
lines = []
|
|
132
|
+
lines.concat(build_header_lines)
|
|
133
|
+
lines.concat(build_description_lines)
|
|
134
|
+
|
|
135
|
+
if detailed
|
|
136
|
+
lines.concat(build_type_specific_lines)
|
|
137
|
+
lines.concat(build_remarks_lines)
|
|
138
|
+
lines.concat(build_example_lines)
|
|
139
|
+
lines.concat(build_related_lines(index))
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
lines.join("\n")
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
protected
|
|
146
|
+
|
|
147
|
+
def build_header_lines
|
|
131
148
|
lines = []
|
|
132
149
|
lines << "# #{name}"
|
|
133
150
|
lines << ""
|
|
@@ -140,36 +157,44 @@ module CF
|
|
|
140
157
|
lines << "- **Implementation:** #{urls[:impl_raw]}"
|
|
141
158
|
end
|
|
142
159
|
lines << ""
|
|
160
|
+
lines
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def build_description_lines
|
|
164
|
+
lines = []
|
|
143
165
|
lines << "## Description"
|
|
144
166
|
lines << brief if brief
|
|
145
167
|
lines << ""
|
|
168
|
+
lines
|
|
169
|
+
end
|
|
146
170
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
lines << remarks
|
|
151
|
-
lines << ""
|
|
152
|
-
end
|
|
171
|
+
def build_type_specific_lines
|
|
172
|
+
[] # Override in subclasses
|
|
173
|
+
end
|
|
153
174
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
lines << example
|
|
159
|
-
lines << "```"
|
|
160
|
-
lines << ""
|
|
161
|
-
end
|
|
175
|
+
def build_remarks_lines
|
|
176
|
+
return [] unless remarks && !remarks.empty?
|
|
177
|
+
["## Remarks", remarks, ""]
|
|
178
|
+
end
|
|
162
179
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
180
|
+
def build_example_lines
|
|
181
|
+
return [] unless example && !example.empty?
|
|
182
|
+
lines = ["## Example"]
|
|
183
|
+
lines << example_brief if example_brief
|
|
184
|
+
lines << "```c"
|
|
185
|
+
lines << example
|
|
186
|
+
lines << "```"
|
|
187
|
+
lines << ""
|
|
188
|
+
lines
|
|
189
|
+
end
|
|
169
190
|
|
|
170
|
-
|
|
191
|
+
def build_related_lines(index)
|
|
192
|
+
return [] unless related && !related.empty?
|
|
193
|
+
["## Related", format_related_items(index), ""]
|
|
171
194
|
end
|
|
172
195
|
|
|
196
|
+
public
|
|
197
|
+
|
|
173
198
|
def format_related_items(index)
|
|
174
199
|
return related.join(", ") unless index
|
|
175
200
|
|
|
@@ -24,58 +24,21 @@ module CF
|
|
|
24
24
|
).compact
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
protected
|
|
28
|
+
|
|
29
|
+
def build_type_specific_lines
|
|
30
|
+
return [] unless entries && !entries.empty?
|
|
31
|
+
|
|
28
32
|
lines = []
|
|
29
|
-
lines << "
|
|
33
|
+
lines << "## Values"
|
|
30
34
|
lines << ""
|
|
31
|
-
lines << "
|
|
32
|
-
lines << "
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
lines << "- **Source:** [include/#{source_file}](#{urls[:blob]})"
|
|
36
|
-
lines << "- **Raw:** #{urls[:raw]}"
|
|
37
|
-
lines << "- **Implementation:** #{urls[:impl_raw]}"
|
|
35
|
+
lines << "| Name | Value | Description |"
|
|
36
|
+
lines << "| --- | --- | --- |"
|
|
37
|
+
entries.each do |entry|
|
|
38
|
+
lines << "| `#{entry.name}` | #{entry.value} | #{entry.description} |"
|
|
38
39
|
end
|
|
39
40
|
lines << ""
|
|
40
|
-
lines
|
|
41
|
-
lines << brief if brief
|
|
42
|
-
lines << ""
|
|
43
|
-
|
|
44
|
-
if detailed
|
|
45
|
-
if entries && !entries.empty?
|
|
46
|
-
lines << "## Values"
|
|
47
|
-
lines << ""
|
|
48
|
-
lines << "| Name | Value | Description |"
|
|
49
|
-
lines << "| --- | --- | --- |"
|
|
50
|
-
entries.each do |entry|
|
|
51
|
-
lines << "| `#{entry.name}` | #{entry.value} | #{entry.description} |"
|
|
52
|
-
end
|
|
53
|
-
lines << ""
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
if remarks && !remarks.empty?
|
|
57
|
-
lines << "## Remarks"
|
|
58
|
-
lines << remarks
|
|
59
|
-
lines << ""
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
if example && !example.empty?
|
|
63
|
-
lines << "## Example"
|
|
64
|
-
lines << example_brief if example_brief
|
|
65
|
-
lines << "```c"
|
|
66
|
-
lines << example
|
|
67
|
-
lines << "```"
|
|
68
|
-
lines << ""
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
if related && !related.empty?
|
|
72
|
-
lines << "## Related"
|
|
73
|
-
lines << format_related_items(index)
|
|
74
|
-
lines << ""
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
lines.join("\n")
|
|
41
|
+
lines
|
|
79
42
|
end
|
|
80
43
|
end
|
|
81
44
|
end
|
|
@@ -38,71 +38,48 @@ module CF
|
|
|
38
38
|
|
|
39
39
|
def to_text(detailed: false, index: nil)
|
|
40
40
|
lines = []
|
|
41
|
-
lines
|
|
42
|
-
lines
|
|
43
|
-
lines
|
|
44
|
-
lines << "- **Category:** #{category}" if category
|
|
45
|
-
if source_file
|
|
46
|
-
urls = source_urls
|
|
47
|
-
lines << "- **Source:** [include/#{source_file}](#{urls[:blob]})"
|
|
48
|
-
lines << "- **Raw:** #{urls[:raw]}"
|
|
49
|
-
lines << "- **Implementation:** #{urls[:impl_raw]}"
|
|
50
|
-
end
|
|
51
|
-
lines << ""
|
|
41
|
+
lines.concat(build_header_lines)
|
|
42
|
+
lines.concat(build_signature_lines)
|
|
43
|
+
lines.concat(build_description_lines)
|
|
52
44
|
|
|
53
|
-
if
|
|
54
|
-
lines
|
|
55
|
-
lines
|
|
56
|
-
lines
|
|
57
|
-
lines
|
|
58
|
-
lines << ""
|
|
45
|
+
if detailed
|
|
46
|
+
lines.concat(build_type_specific_lines)
|
|
47
|
+
lines.concat(build_remarks_lines)
|
|
48
|
+
lines.concat(build_example_lines)
|
|
49
|
+
lines.concat(build_related_lines(index))
|
|
59
50
|
end
|
|
60
51
|
|
|
61
|
-
lines
|
|
62
|
-
|
|
63
|
-
lines << ""
|
|
52
|
+
lines.join("\n")
|
|
53
|
+
end
|
|
64
54
|
|
|
65
|
-
|
|
66
|
-
if parameters && !parameters.empty?
|
|
67
|
-
lines << "## Parameters"
|
|
68
|
-
lines << ""
|
|
69
|
-
lines << "| Parameter | Description |"
|
|
70
|
-
lines << "| --- | --- |"
|
|
71
|
-
parameters.each do |param|
|
|
72
|
-
lines << "| `#{param.name}` | #{param.description} |"
|
|
73
|
-
end
|
|
74
|
-
lines << ""
|
|
75
|
-
end
|
|
55
|
+
protected
|
|
76
56
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
end
|
|
57
|
+
def build_signature_lines
|
|
58
|
+
return [] unless signature
|
|
59
|
+
["## Signature", "```c", signature, "```", ""]
|
|
60
|
+
end
|
|
82
61
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
lines << remarks
|
|
86
|
-
lines << ""
|
|
87
|
-
end
|
|
62
|
+
def build_type_specific_lines
|
|
63
|
+
lines = []
|
|
88
64
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
lines << ""
|
|
65
|
+
if parameters && !parameters.empty?
|
|
66
|
+
lines << "## Parameters"
|
|
67
|
+
lines << ""
|
|
68
|
+
lines << "| Parameter | Description |"
|
|
69
|
+
lines << "| --- | --- |"
|
|
70
|
+
parameters.each do |param|
|
|
71
|
+
lines << "| `#{param.name}` | #{param.description} |"
|
|
96
72
|
end
|
|
73
|
+
lines << ""
|
|
74
|
+
end
|
|
97
75
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
end
|
|
76
|
+
if return_value && !return_value.empty?
|
|
77
|
+
lines << "## Return Value"
|
|
78
|
+
lines << return_value
|
|
79
|
+
lines << ""
|
|
103
80
|
end
|
|
104
81
|
|
|
105
|
-
lines
|
|
82
|
+
lines
|
|
106
83
|
end
|
|
107
84
|
end
|
|
108
85
|
end
|
|
@@ -24,58 +24,21 @@ module CF
|
|
|
24
24
|
).compact
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
protected
|
|
28
|
+
|
|
29
|
+
def build_type_specific_lines
|
|
30
|
+
return [] unless members && !members.empty?
|
|
31
|
+
|
|
28
32
|
lines = []
|
|
29
|
-
lines << "
|
|
33
|
+
lines << "## Members"
|
|
30
34
|
lines << ""
|
|
31
|
-
lines << "
|
|
32
|
-
lines << "
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
lines << "- **Source:** [include/#{source_file}](#{urls[:blob]})"
|
|
36
|
-
lines << "- **Raw:** #{urls[:raw]}"
|
|
37
|
-
lines << "- **Implementation:** #{urls[:impl_raw]}"
|
|
35
|
+
lines << "| Member | Description |"
|
|
36
|
+
lines << "| --- | --- |"
|
|
37
|
+
members.each do |member|
|
|
38
|
+
lines << "| `#{member.declaration}` | #{member.description} |"
|
|
38
39
|
end
|
|
39
40
|
lines << ""
|
|
40
|
-
lines
|
|
41
|
-
lines << brief if brief
|
|
42
|
-
lines << ""
|
|
43
|
-
|
|
44
|
-
if detailed
|
|
45
|
-
if members && !members.empty?
|
|
46
|
-
lines << "## Members"
|
|
47
|
-
lines << ""
|
|
48
|
-
lines << "| Member | Description |"
|
|
49
|
-
lines << "| --- | --- |"
|
|
50
|
-
members.each do |member|
|
|
51
|
-
lines << "| `#{member.declaration}` | #{member.description} |"
|
|
52
|
-
end
|
|
53
|
-
lines << ""
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
if remarks && !remarks.empty?
|
|
57
|
-
lines << "## Remarks"
|
|
58
|
-
lines << remarks
|
|
59
|
-
lines << ""
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
if example && !example.empty?
|
|
63
|
-
lines << "## Example"
|
|
64
|
-
lines << example_brief if example_brief
|
|
65
|
-
lines << "```c"
|
|
66
|
-
lines << example
|
|
67
|
-
lines << "```"
|
|
68
|
-
lines << ""
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
if related && !related.empty?
|
|
72
|
-
lines << "## Related"
|
|
73
|
-
lines << format_related_items(index)
|
|
74
|
-
lines << ""
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
lines.join("\n")
|
|
41
|
+
lines
|
|
79
42
|
end
|
|
80
43
|
end
|
|
81
44
|
end
|
data/lib/cf/mcp/server.rb
CHANGED
|
@@ -91,86 +91,23 @@ module CF
|
|
|
91
91
|
# Build a rack app with automatic header downloading and indexing
|
|
92
92
|
# This is the shared entry point for both config.ru and CLI
|
|
93
93
|
def self.build_rack_app(root: nil, download: false)
|
|
94
|
-
require_relative "
|
|
95
|
-
require_relative "topic_parser"
|
|
96
|
-
require_relative "index"
|
|
97
|
-
require_relative "downloader"
|
|
94
|
+
require_relative "index_builder"
|
|
98
95
|
|
|
99
|
-
|
|
96
|
+
builder = IndexBuilder.new(root: root, download: download)
|
|
100
97
|
|
|
101
|
-
unless
|
|
102
|
-
raise "Headers directory not found: #{headers_path}. Use root: or download: true"
|
|
98
|
+
unless builder.valid?
|
|
99
|
+
raise "Headers directory not found: #{builder.headers_path}. Use root: or download: true"
|
|
103
100
|
end
|
|
104
101
|
|
|
105
|
-
warn "Parsing headers from: #{headers_path}"
|
|
106
|
-
index =
|
|
102
|
+
warn "Parsing headers from: #{builder.headers_path}"
|
|
103
|
+
index = builder.build do |event, path, count|
|
|
104
|
+
warn "Indexed #{count} topics from: #{path}" if event == :topics_indexed
|
|
105
|
+
end
|
|
107
106
|
warn "Indexed #{index.stats[:total]} items (#{index.stats[:functions]} functions, #{index.stats[:structs]} structs, #{index.stats[:enums]} enums)"
|
|
108
107
|
|
|
109
108
|
new(index).rack_app
|
|
110
109
|
end
|
|
111
110
|
|
|
112
|
-
def self.resolve_headers_path(root:, download:)
|
|
113
|
-
return root if root
|
|
114
|
-
return ENV["CF_HEADERS_PATH"] if ENV["CF_HEADERS_PATH"]
|
|
115
|
-
|
|
116
|
-
if download
|
|
117
|
-
warn "Downloading Cute Framework headers from GitHub..."
|
|
118
|
-
downloader = Downloader.new
|
|
119
|
-
path = downloader.download_and_extract
|
|
120
|
-
warn "Downloaded headers to: #{path}"
|
|
121
|
-
return path
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
File.expand_path("~/Work/GitHub/pusewicz/cute_framework/include")
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
def self.build_index(headers_path)
|
|
128
|
-
parser = Parser.new
|
|
129
|
-
index = Index.new
|
|
130
|
-
|
|
131
|
-
parser.parse_directory(headers_path).each do |item|
|
|
132
|
-
index.add(item)
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
# Parse topics if available
|
|
136
|
-
topics_path = find_topics_path(headers_path)
|
|
137
|
-
if topics_path && File.directory?(topics_path)
|
|
138
|
-
topic_parser = TopicParser.new
|
|
139
|
-
topic_parser.parse_directory(topics_path).each do |topic|
|
|
140
|
-
refine_topic_references(topic, index)
|
|
141
|
-
index.add(topic)
|
|
142
|
-
end
|
|
143
|
-
warn "Indexed #{index.stats[:topics]} topics from: #{topics_path}"
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
index
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
def self.find_topics_path(headers_path)
|
|
150
|
-
base = File.dirname(headers_path)
|
|
151
|
-
topics_path = File.join(base, "docs", "topics")
|
|
152
|
-
return topics_path if File.directory?(topics_path)
|
|
153
|
-
|
|
154
|
-
topics_path = File.join(base, "topics")
|
|
155
|
-
return topics_path if File.directory?(topics_path)
|
|
156
|
-
|
|
157
|
-
nil
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
def self.refine_topic_references(topic, index)
|
|
161
|
-
topic.struct_references.dup.each do |ref|
|
|
162
|
-
item = index.find(ref)
|
|
163
|
-
next unless item
|
|
164
|
-
|
|
165
|
-
if item.type == :enum
|
|
166
|
-
topic.struct_references.delete(ref)
|
|
167
|
-
topic.enum_references << ref unless topic.enum_references.include?(ref)
|
|
168
|
-
end
|
|
169
|
-
end
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
private_class_method :resolve_headers_path, :build_index, :find_topics_path, :refine_topic_references
|
|
173
|
-
|
|
174
111
|
def initialize(index)
|
|
175
112
|
@index = index
|
|
176
113
|
end
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "mcp"
|
|
4
|
+
require_relative "response_helpers"
|
|
4
5
|
|
|
5
6
|
module CF
|
|
6
7
|
module MCP
|
|
7
8
|
module Tools
|
|
8
9
|
class FindRelated < ::MCP::Tool
|
|
10
|
+
extend ResponseHelpers
|
|
11
|
+
|
|
9
12
|
tool_name "cf_find_related"
|
|
10
13
|
description "Find all items related to a given Cute Framework item (bidirectional relationship search)"
|
|
11
14
|
|
|
@@ -63,14 +66,6 @@ module CF
|
|
|
63
66
|
|
|
64
67
|
text_response(lines.join("\n"))
|
|
65
68
|
end
|
|
66
|
-
|
|
67
|
-
def self.text_response(text)
|
|
68
|
-
::MCP::Tool::Response.new([{type: "text", text: text}])
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def self.error_response(message)
|
|
72
|
-
::MCP::Tool::Response.new([{type: "text", text: "Error: #{message}"}], error: true)
|
|
73
|
-
end
|
|
74
69
|
end
|
|
75
70
|
end
|
|
76
71
|
end
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "mcp"
|
|
4
|
+
require_relative "response_helpers"
|
|
4
5
|
|
|
5
6
|
module CF
|
|
6
7
|
module MCP
|
|
7
8
|
module Tools
|
|
8
9
|
class GetDetails < ::MCP::Tool
|
|
10
|
+
extend ResponseHelpers
|
|
11
|
+
|
|
9
12
|
tool_name "cf_get_details"
|
|
10
13
|
description "Get detailed documentation for a specific Cute Framework item by exact name"
|
|
11
14
|
|
|
@@ -50,14 +53,6 @@ module CF
|
|
|
50
53
|
text_response(output)
|
|
51
54
|
end
|
|
52
55
|
end
|
|
53
|
-
|
|
54
|
-
def self.text_response(text)
|
|
55
|
-
::MCP::Tool::Response.new([{type: "text", text: text}])
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def self.error_response(message)
|
|
59
|
-
::MCP::Tool::Response.new([{type: "text", text: "Error: #{message}"}], error: true)
|
|
60
|
-
end
|
|
61
56
|
end
|
|
62
57
|
end
|
|
63
58
|
end
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "mcp"
|
|
4
|
+
require_relative "response_helpers"
|
|
4
5
|
|
|
5
6
|
module CF
|
|
6
7
|
module MCP
|
|
7
8
|
module Tools
|
|
8
9
|
class GetTopic < ::MCP::Tool
|
|
10
|
+
extend ResponseHelpers
|
|
11
|
+
|
|
9
12
|
tool_name "cf_get_topic"
|
|
10
13
|
description "Get the full content of a Cute Framework topic guide document"
|
|
11
14
|
|
|
@@ -39,14 +42,6 @@ module CF
|
|
|
39
42
|
text_response(topic.to_text(detailed: true, index: index))
|
|
40
43
|
end
|
|
41
44
|
end
|
|
42
|
-
|
|
43
|
-
def self.text_response(text)
|
|
44
|
-
::MCP::Tool::Response.new([{type: "text", text: text}])
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def self.error_response(message)
|
|
48
|
-
::MCP::Tool::Response.new([{type: "text", text: "Error: #{message}"}], error: true)
|
|
49
|
-
end
|
|
50
45
|
end
|
|
51
46
|
end
|
|
52
47
|
end
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "mcp"
|
|
4
|
+
require_relative "response_helpers"
|
|
4
5
|
|
|
5
6
|
module CF
|
|
6
7
|
module MCP
|
|
7
8
|
module Tools
|
|
8
9
|
class ListCategory < ::MCP::Tool
|
|
10
|
+
extend ResponseHelpers
|
|
11
|
+
|
|
9
12
|
tool_name "cf_list_category"
|
|
10
13
|
description "List all items in a specific category, or list all available categories"
|
|
11
14
|
|
|
@@ -63,14 +66,6 @@ module CF
|
|
|
63
66
|
end
|
|
64
67
|
end
|
|
65
68
|
end
|
|
66
|
-
|
|
67
|
-
def self.text_response(text)
|
|
68
|
-
::MCP::Tool::Response.new([{type: "text", text: text}])
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def self.error_response(message)
|
|
72
|
-
::MCP::Tool::Response.new([{type: "text", text: "Error: #{message}"}], error: true)
|
|
73
|
-
end
|
|
74
69
|
end
|
|
75
70
|
end
|
|
76
71
|
end
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "mcp"
|
|
4
|
+
require_relative "response_helpers"
|
|
4
5
|
|
|
5
6
|
module CF
|
|
6
7
|
module MCP
|
|
7
8
|
module Tools
|
|
8
9
|
class ListTopics < ::MCP::Tool
|
|
10
|
+
extend ResponseHelpers
|
|
11
|
+
|
|
9
12
|
tool_name "cf_list_topics"
|
|
10
13
|
description "List all Cute Framework topic guides, optionally filtered by category or in recommended reading order"
|
|
11
14
|
|
|
@@ -50,14 +53,6 @@ module CF
|
|
|
50
53
|
end
|
|
51
54
|
|
|
52
55
|
CATEGORY_TIP = "Use `cf_list_topics` without a category to see all available topics."
|
|
53
|
-
|
|
54
|
-
def self.text_response(text)
|
|
55
|
-
::MCP::Tool::Response.new([{type: "text", text: text}])
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def self.error_response(message)
|
|
59
|
-
::MCP::Tool::Response.new([{type: "text", text: "Error: #{message}"}], error: true)
|
|
60
|
-
end
|
|
61
56
|
end
|
|
62
57
|
end
|
|
63
58
|
end
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "mcp"
|
|
4
|
+
require_relative "response_helpers"
|
|
4
5
|
|
|
5
6
|
module CF
|
|
6
7
|
module MCP
|
|
7
8
|
module Tools
|
|
8
9
|
class MemberSearch < ::MCP::Tool
|
|
10
|
+
extend ResponseHelpers
|
|
11
|
+
|
|
9
12
|
tool_name "cf_member_search"
|
|
10
13
|
description "Search Cute Framework structs by member name or type"
|
|
11
14
|
|
|
@@ -62,14 +65,6 @@ module CF
|
|
|
62
65
|
|
|
63
66
|
text_response(lines.join("\n"))
|
|
64
67
|
end
|
|
65
|
-
|
|
66
|
-
def self.text_response(text)
|
|
67
|
-
::MCP::Tool::Response.new([{type: "text", text: text}])
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def self.error_response(message)
|
|
71
|
-
::MCP::Tool::Response.new([{type: "text", text: "Error: #{message}"}], error: true)
|
|
72
|
-
end
|
|
73
68
|
end
|
|
74
69
|
end
|
|
75
70
|
end
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "mcp"
|
|
4
|
+
require_relative "response_helpers"
|
|
4
5
|
|
|
5
6
|
module CF
|
|
6
7
|
module MCP
|
|
7
8
|
module Tools
|
|
8
9
|
class ParameterSearch < ::MCP::Tool
|
|
10
|
+
extend ResponseHelpers
|
|
11
|
+
|
|
9
12
|
tool_name "cf_parameter_search"
|
|
10
13
|
description "Find Cute Framework functions by parameter or return type"
|
|
11
14
|
|
|
@@ -88,14 +91,6 @@ module CF
|
|
|
88
91
|
|
|
89
92
|
text_response(lines.join("\n"))
|
|
90
93
|
end
|
|
91
|
-
|
|
92
|
-
def self.text_response(text)
|
|
93
|
-
::MCP::Tool::Response.new([{type: "text", text: text}])
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
def self.error_response(message)
|
|
97
|
-
::MCP::Tool::Response.new([{type: "text", text: "Error: #{message}"}], error: true)
|
|
98
|
-
end
|
|
99
94
|
end
|
|
100
95
|
end
|
|
101
96
|
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "mcp"
|
|
4
|
+
|
|
5
|
+
module CF
|
|
6
|
+
module MCP
|
|
7
|
+
module Tools
|
|
8
|
+
module ResponseHelpers
|
|
9
|
+
def text_response(text)
|
|
10
|
+
::MCP::Tool::Response.new([{type: "text", text: text}])
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def error_response(message)
|
|
14
|
+
::MCP::Tool::Response.new([{type: "text", text: "Error: #{message}"}], error: true)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "mcp"
|
|
4
|
+
require_relative "response_helpers"
|
|
5
|
+
require_relative "search_result_formatter"
|
|
4
6
|
|
|
5
7
|
module CF
|
|
6
8
|
module MCP
|
|
7
9
|
module Tools
|
|
8
10
|
class SearchEnums < ::MCP::Tool
|
|
11
|
+
extend ResponseHelpers
|
|
12
|
+
extend SearchResultFormatter
|
|
13
|
+
|
|
9
14
|
tool_name "cf_search_enums"
|
|
10
15
|
description "Search Cute Framework enums"
|
|
11
16
|
|
|
@@ -27,29 +32,15 @@ module CF
|
|
|
27
32
|
|
|
28
33
|
results = index.search(query, type: :enum, category: category, limit: limit)
|
|
29
34
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
footer = "\n\n#{DETAILS_TIP}"
|
|
41
|
-
footer += "\nTo find more results, narrow your search with a `category` filter." if results.size >= limit
|
|
42
|
-
|
|
43
|
-
text_response("#{header}\n\n#{formatted}#{footer}")
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def self.text_response(text)
|
|
48
|
-
::MCP::Tool::Response.new([{type: "text", text: text}])
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def self.error_response(message)
|
|
52
|
-
::MCP::Tool::Response.new([{type: "text", text: "Error: #{message}"}], error: true)
|
|
35
|
+
text = format_search_results(
|
|
36
|
+
results,
|
|
37
|
+
query: query,
|
|
38
|
+
type_label: "enum(s)",
|
|
39
|
+
limit: limit,
|
|
40
|
+
details_tip: DETAILS_TIP,
|
|
41
|
+
filter_suggestion: "To find more results, narrow your search with a `category` filter."
|
|
42
|
+
)
|
|
43
|
+
text_response(text)
|
|
53
44
|
end
|
|
54
45
|
end
|
|
55
46
|
end
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "mcp"
|
|
4
|
+
require_relative "response_helpers"
|
|
5
|
+
require_relative "search_result_formatter"
|
|
4
6
|
|
|
5
7
|
module CF
|
|
6
8
|
module MCP
|
|
7
9
|
module Tools
|
|
8
10
|
class SearchFunctions < ::MCP::Tool
|
|
11
|
+
extend ResponseHelpers
|
|
12
|
+
extend SearchResultFormatter
|
|
13
|
+
|
|
9
14
|
tool_name "cf_search_functions"
|
|
10
15
|
description "Search Cute Framework functions"
|
|
11
16
|
|
|
@@ -27,29 +32,15 @@ module CF
|
|
|
27
32
|
|
|
28
33
|
results = index.search(query, type: :function, category: category, limit: limit)
|
|
29
34
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
footer = "\n\n#{DETAILS_TIP}"
|
|
41
|
-
footer += "\nTo find more results, narrow your search with a `category` filter." if results.size >= limit
|
|
42
|
-
|
|
43
|
-
text_response("#{header}\n\n#{formatted}#{footer}")
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def self.text_response(text)
|
|
48
|
-
::MCP::Tool::Response.new([{type: "text", text: text}])
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def self.error_response(message)
|
|
52
|
-
::MCP::Tool::Response.new([{type: "text", text: "Error: #{message}"}], error: true)
|
|
35
|
+
text = format_search_results(
|
|
36
|
+
results,
|
|
37
|
+
query: query,
|
|
38
|
+
type_label: "function(s)",
|
|
39
|
+
limit: limit,
|
|
40
|
+
details_tip: DETAILS_TIP,
|
|
41
|
+
filter_suggestion: "To find more results, narrow your search with a `category` filter."
|
|
42
|
+
)
|
|
43
|
+
text_response(text)
|
|
53
44
|
end
|
|
54
45
|
end
|
|
55
46
|
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CF
|
|
4
|
+
module MCP
|
|
5
|
+
module Tools
|
|
6
|
+
module SearchResultFormatter
|
|
7
|
+
def format_search_results(results, query:, type_label:, limit:, details_tip:, filter_suggestion: nil)
|
|
8
|
+
if results.empty?
|
|
9
|
+
# Use plural form for "no results" message (e.g., "No functions found")
|
|
10
|
+
plural_label = type_label.sub(/\(s\)$/, "s")
|
|
11
|
+
return "No #{plural_label} found for '#{query}'"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
formatted = results.map(&:to_summary).join("\n")
|
|
15
|
+
|
|
16
|
+
header = if results.size >= limit
|
|
17
|
+
"Found #{results.size} #{type_label} (limit reached, more may exist):"
|
|
18
|
+
else
|
|
19
|
+
"Found #{results.size} #{type_label}:"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
footer = "\n\n#{details_tip}"
|
|
23
|
+
footer += "\n#{filter_suggestion}" if results.size >= limit && filter_suggestion
|
|
24
|
+
|
|
25
|
+
"#{header}\n\n#{formatted}#{footer}"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "mcp"
|
|
4
|
+
require_relative "response_helpers"
|
|
5
|
+
require_relative "search_result_formatter"
|
|
4
6
|
|
|
5
7
|
module CF
|
|
6
8
|
module MCP
|
|
7
9
|
module Tools
|
|
8
10
|
class SearchStructs < ::MCP::Tool
|
|
11
|
+
extend ResponseHelpers
|
|
12
|
+
extend SearchResultFormatter
|
|
13
|
+
|
|
9
14
|
tool_name "cf_search_structs"
|
|
10
15
|
description "Search Cute Framework structs"
|
|
11
16
|
|
|
@@ -27,29 +32,15 @@ module CF
|
|
|
27
32
|
|
|
28
33
|
results = index.search(query, type: :struct, category: category, limit: limit)
|
|
29
34
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
footer = "\n\n#{DETAILS_TIP}"
|
|
41
|
-
footer += "\nTo find more results, narrow your search with a `category` filter." if results.size >= limit
|
|
42
|
-
|
|
43
|
-
text_response("#{header}\n\n#{formatted}#{footer}")
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def self.text_response(text)
|
|
48
|
-
::MCP::Tool::Response.new([{type: "text", text: text}])
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def self.error_response(message)
|
|
52
|
-
::MCP::Tool::Response.new([{type: "text", text: "Error: #{message}"}], error: true)
|
|
35
|
+
text = format_search_results(
|
|
36
|
+
results,
|
|
37
|
+
query: query,
|
|
38
|
+
type_label: "struct(s)",
|
|
39
|
+
limit: limit,
|
|
40
|
+
details_tip: DETAILS_TIP,
|
|
41
|
+
filter_suggestion: "To find more results, narrow your search with a `category` filter."
|
|
42
|
+
)
|
|
43
|
+
text_response(text)
|
|
53
44
|
end
|
|
54
45
|
end
|
|
55
46
|
end
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "mcp"
|
|
4
|
+
require_relative "response_helpers"
|
|
5
|
+
require_relative "search_result_formatter"
|
|
4
6
|
|
|
5
7
|
module CF
|
|
6
8
|
module MCP
|
|
7
9
|
module Tools
|
|
8
10
|
class SearchTool < ::MCP::Tool
|
|
11
|
+
extend ResponseHelpers
|
|
12
|
+
extend SearchResultFormatter
|
|
13
|
+
|
|
9
14
|
tool_name "cf_search"
|
|
10
15
|
description "Search Cute Framework documentation across all types (functions, structs, enums, topics)"
|
|
11
16
|
|
|
@@ -28,29 +33,15 @@ module CF
|
|
|
28
33
|
|
|
29
34
|
results = index.search(query, type: type, category: category, limit: limit)
|
|
30
35
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
footer = "\n\n#{DETAILS_TIP}"
|
|
42
|
-
footer += "\nTo find more results, narrow your search with `type` or `category` filters." if results.size >= limit
|
|
43
|
-
|
|
44
|
-
text_response("#{header}\n\n#{formatted}#{footer}")
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def self.text_response(text)
|
|
49
|
-
::MCP::Tool::Response.new([{type: "text", text: text}])
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def self.error_response(message)
|
|
53
|
-
::MCP::Tool::Response.new([{type: "text", text: "Error: #{message}"}], error: true)
|
|
36
|
+
text = format_search_results(
|
|
37
|
+
results,
|
|
38
|
+
query: query,
|
|
39
|
+
type_label: "result(s)",
|
|
40
|
+
limit: limit,
|
|
41
|
+
details_tip: DETAILS_TIP,
|
|
42
|
+
filter_suggestion: "To find more results, narrow your search with `type` or `category` filters."
|
|
43
|
+
)
|
|
44
|
+
text_response(text)
|
|
54
45
|
end
|
|
55
46
|
end
|
|
56
47
|
end
|
data/lib/cf/mcp/version.rb
CHANGED
data/lib/cf/mcp.rb
CHANGED
|
@@ -8,6 +8,7 @@ require_relative "mcp/models/struct_doc"
|
|
|
8
8
|
require_relative "mcp/models/enum_doc"
|
|
9
9
|
require_relative "mcp/parser"
|
|
10
10
|
require_relative "mcp/index"
|
|
11
|
+
require_relative "mcp/index_builder"
|
|
11
12
|
require_relative "mcp/server"
|
|
12
13
|
require_relative "mcp/downloader"
|
|
13
14
|
require_relative "mcp/cli"
|
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.12.
|
|
4
|
+
version: 0.12.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Piotr Usewicz
|
|
@@ -99,6 +99,7 @@ files:
|
|
|
99
99
|
- lib/cf/mcp/cli.rb
|
|
100
100
|
- lib/cf/mcp/downloader.rb
|
|
101
101
|
- lib/cf/mcp/index.rb
|
|
102
|
+
- lib/cf/mcp/index_builder.rb
|
|
102
103
|
- lib/cf/mcp/models/doc_item.rb
|
|
103
104
|
- lib/cf/mcp/models/enum_doc.rb
|
|
104
105
|
- lib/cf/mcp/models/function_doc.rb
|
|
@@ -116,8 +117,10 @@ files:
|
|
|
116
117
|
- lib/cf/mcp/tools/list_topics.rb
|
|
117
118
|
- lib/cf/mcp/tools/member_search.rb
|
|
118
119
|
- lib/cf/mcp/tools/parameter_search.rb
|
|
120
|
+
- lib/cf/mcp/tools/response_helpers.rb
|
|
119
121
|
- lib/cf/mcp/tools/search_enums.rb
|
|
120
122
|
- lib/cf/mcp/tools/search_functions.rb
|
|
123
|
+
- lib/cf/mcp/tools/search_result_formatter.rb
|
|
121
124
|
- lib/cf/mcp/tools/search_structs.rb
|
|
122
125
|
- lib/cf/mcp/tools/search_tool.rb
|
|
123
126
|
- lib/cf/mcp/topic_parser.rb
|