cf-mcp 0.10.0 → 0.12.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 +4 -4
- data/CHANGELOG.md +29 -0
- data/README.md +3 -5
- data/config.ru +2 -22
- data/lib/cf/mcp/cli.rb +20 -40
- data/lib/cf/mcp/server.rb +75 -48
- data/lib/cf/mcp/templates/index.erb +2 -6
- data/lib/cf/mcp/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0f3d843ae9e0087f798cad60be61d347da0db7c1f26e5bee5050eccda2a38b29
|
|
4
|
+
data.tar.gz: 98735400ef25d3a51d6d984a0daf12141c1c96b6843922069d71bb24a4183bdc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fb5143e02aca5453221893bdabd3cb945912cfa83c7693f34d0cba99ea1cbde14a93d4238d3dfb92ab1e39026f5b98ab416de2a40eb68f262745f028e6aecf68
|
|
7
|
+
data.tar.gz: 918e16127b4a29c8e0d33fe162b5d1788d14c28f3e1c55774d97204ad1805a38c1407f6d10b07072fe9b55c1998436f833fb957ee4f3739434887e94eea84f07
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,33 @@ 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.0] - 2026-01-14
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Renamed `CombinedServer` to `HTTPServer` for clarity
|
|
13
|
+
- Updated config.ru to use the new `HTTPServer` class
|
|
14
|
+
|
|
15
|
+
### Removed
|
|
16
|
+
|
|
17
|
+
- SSE transport endpoint (HTTP streamable transport is now the only HTTP option)
|
|
18
|
+
|
|
19
|
+
## [0.10.1] - 2026-01-14
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- `--host` CLI option for binding address (defaults to `0.0.0.0`)
|
|
24
|
+
- `CombinedServer.build_rack_app` class method for shared boot logic
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- Unified boot process: config.ru and CLI now share the same initialization logic
|
|
29
|
+
- Simplified config.ru to a single line using `CombinedServer.build_rack_app`
|
|
30
|
+
|
|
31
|
+
### Removed
|
|
32
|
+
|
|
33
|
+
- `Procfile` (Fly.io uses config.ru directly)
|
|
34
|
+
|
|
8
35
|
## [0.10.0] - 2026-01-14
|
|
9
36
|
|
|
10
37
|
### Added
|
|
@@ -112,6 +139,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
112
139
|
- `cf_list_category` - List items by category
|
|
113
140
|
- `cf_get_details` - Get full documentation by name
|
|
114
141
|
|
|
142
|
+
[0.12.0]: https://github.com/pusewicz/cf-mcp/compare/v0.10.1...v0.12.0
|
|
143
|
+
[0.10.1]: https://github.com/pusewicz/cf-mcp/compare/v0.10.0...v0.10.1
|
|
115
144
|
[0.10.0]: https://github.com/pusewicz/cf-mcp/compare/v0.9.3...v0.10.0
|
|
116
145
|
[0.9.3]: https://github.com/pusewicz/cf-mcp/compare/v0.9.2...v0.9.3
|
|
117
146
|
[0.9.2]: https://github.com/pusewicz/cf-mcp/compare/v0.9.1...v0.9.2
|
data/README.md
CHANGED
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
CF::MCP is an MCP server providing documentation tools for the [Cute Framework](https://github.com/RandyGaul/cute_framework), a C/C++ 2D game framework.
|
|
4
4
|
|
|
5
|
-
The MCP server supports
|
|
5
|
+
The MCP server supports two modes of operation:
|
|
6
6
|
|
|
7
7
|
- **STDIO Mode**: Communicates via standard input and output streams, suitable for integration with CLI tools and desktop applications.
|
|
8
|
-
- **HTTP Mode**: Operates as
|
|
9
|
-
- **SSE Mode**: Operates as a stateful HTTP server with Server-Sent Events support, enabling real-time notifications and streaming responses.
|
|
8
|
+
- **HTTP Mode**: Operates as an HTTP server with a web interface at the root and MCP endpoint at `/http`.
|
|
10
9
|
|
|
11
10
|
## Features
|
|
12
11
|
|
|
@@ -33,8 +32,7 @@ To start the MCP server, run the following command in your terminal:
|
|
|
33
32
|
|
|
34
33
|
```bash
|
|
35
34
|
cf-mcp stdio --root /path/to/cute_framework_project # STDIO mode
|
|
36
|
-
cf-mcp http --root /path/to/cute_framework_project # HTTP mode
|
|
37
|
-
cf-mcp sse --root /path/to/cute_framework_project # SSE mode (stateful, real-time)
|
|
35
|
+
cf-mcp http --root /path/to/cute_framework_project # HTTP mode with web UI
|
|
38
36
|
```
|
|
39
37
|
|
|
40
38
|
## Development
|
data/config.ru
CHANGED
|
@@ -2,25 +2,5 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "lib/cf/mcp"
|
|
4
4
|
|
|
5
|
-
#
|
|
6
|
-
|
|
7
|
-
downloader = CF::MCP::Downloader.new
|
|
8
|
-
headers_path = downloader.download_and_extract
|
|
9
|
-
warn "Using headers from: #{headers_path}"
|
|
10
|
-
|
|
11
|
-
# Build the index
|
|
12
|
-
warn "Parsing headers..."
|
|
13
|
-
parser = CF::MCP::Parser.new
|
|
14
|
-
index = CF::MCP::Index.new
|
|
15
|
-
|
|
16
|
-
parser.parse_directory(headers_path).each do |item|
|
|
17
|
-
index.add(item)
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
warn "Indexed #{index.stats[:total]} items (#{index.stats[:functions]} functions, #{index.stats[:structs]} structs, #{index.stats[:enums]} enums)"
|
|
21
|
-
|
|
22
|
-
# Create and run the combined server with both SSE and HTTP transports
|
|
23
|
-
# - / and /sse - SSE transport (stateful, for Claude Desktop)
|
|
24
|
-
# - /http - HTTP transport (stateless, for simple integrations)
|
|
25
|
-
server = CF::MCP::CombinedServer.new(index)
|
|
26
|
-
run server.rack_app
|
|
5
|
+
# Build and run the HTTP server with automatic header downloading
|
|
6
|
+
run CF::MCP::HTTPServer.build_rack_app(download: true)
|
data/lib/cf/mcp/cli.rb
CHANGED
|
@@ -22,11 +22,7 @@ module CF
|
|
|
22
22
|
when :stdio
|
|
23
23
|
run_server(:stdio)
|
|
24
24
|
when :http
|
|
25
|
-
|
|
26
|
-
when :sse
|
|
27
|
-
run_server(:sse)
|
|
28
|
-
when :combined
|
|
29
|
-
run_combined_server
|
|
25
|
+
run_http_server
|
|
30
26
|
when :help
|
|
31
27
|
puts @option_parser
|
|
32
28
|
else
|
|
@@ -41,6 +37,7 @@ module CF
|
|
|
41
37
|
options = {
|
|
42
38
|
command: nil,
|
|
43
39
|
port: nil,
|
|
40
|
+
host: "0.0.0.0",
|
|
44
41
|
root: nil,
|
|
45
42
|
download: false
|
|
46
43
|
}
|
|
@@ -50,9 +47,7 @@ module CF
|
|
|
50
47
|
opts.separator ""
|
|
51
48
|
opts.separator "Commands:"
|
|
52
49
|
opts.separator " stdio Run in STDIO mode (for CLI integration)"
|
|
53
|
-
opts.separator " http Run as HTTP server
|
|
54
|
-
opts.separator " sse Run as SSE server (stateful with real-time updates)"
|
|
55
|
-
opts.separator " combined Run as combined server (SSE + HTTP with web interface)"
|
|
50
|
+
opts.separator " http Run as HTTP server with web interface"
|
|
56
51
|
opts.separator ""
|
|
57
52
|
opts.separator "Options:"
|
|
58
53
|
|
|
@@ -60,10 +55,14 @@ module CF
|
|
|
60
55
|
options[:root] = path
|
|
61
56
|
end
|
|
62
57
|
|
|
63
|
-
opts.on("-p", "--port PORT", Integer, "Port for HTTP
|
|
58
|
+
opts.on("-p", "--port PORT", Integer, "Port for HTTP server (default: 9292)") do |port|
|
|
64
59
|
options[:port] = port
|
|
65
60
|
end
|
|
66
61
|
|
|
62
|
+
opts.on("-H", "--host HOST", "Host to bind to (default: 0.0.0.0)") do |host|
|
|
63
|
+
options[:host] = host
|
|
64
|
+
end
|
|
65
|
+
|
|
67
66
|
opts.on("-d", "--download", "Download Cute Framework headers from GitHub") do
|
|
68
67
|
options[:download] = true
|
|
69
68
|
end
|
|
@@ -83,7 +82,7 @@ module CF
|
|
|
83
82
|
# Parse command from remaining args
|
|
84
83
|
if options[:command].nil? && !@args.empty?
|
|
85
84
|
command = @args.shift.to_sym
|
|
86
|
-
options[:command] = command if [:stdio, :http
|
|
85
|
+
options[:command] = command if [:stdio, :http].include?(command)
|
|
87
86
|
end
|
|
88
87
|
|
|
89
88
|
options[:command] ||= :help
|
|
@@ -105,43 +104,24 @@ module CF
|
|
|
105
104
|
warn "Indexed #{index.stats[:total]} items (#{index.stats[:functions]} functions, #{index.stats[:structs]} structs, #{index.stats[:enums]} enums)"
|
|
106
105
|
|
|
107
106
|
server = Server.new(index)
|
|
108
|
-
|
|
109
|
-
case mode
|
|
110
|
-
when :stdio
|
|
111
|
-
server.run_stdio
|
|
112
|
-
when :http
|
|
113
|
-
port = @options[:port] || 9292
|
|
114
|
-
server.run_http(port: port)
|
|
115
|
-
when :sse
|
|
116
|
-
port = @options[:port] || 9393
|
|
117
|
-
server.run_sse(port: port)
|
|
118
|
-
end
|
|
107
|
+
server.run_stdio
|
|
119
108
|
end
|
|
120
109
|
|
|
121
|
-
def
|
|
122
|
-
require "rack"
|
|
110
|
+
def run_http_server
|
|
123
111
|
require "rackup"
|
|
124
112
|
|
|
125
|
-
headers_path = resolve_headers_path
|
|
126
|
-
|
|
127
|
-
unless File.directory?(headers_path)
|
|
128
|
-
warn "Error: Headers directory not found: #{headers_path}"
|
|
129
|
-
warn "Use --root to specify the path to Cute Framework headers"
|
|
130
|
-
warn "Or use --download to fetch headers from GitHub"
|
|
131
|
-
exit 1
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
warn "Parsing headers from: #{headers_path}"
|
|
135
|
-
index = build_index(headers_path)
|
|
136
|
-
warn "Indexed #{index.stats[:total]} items (#{index.stats[:functions]} functions, #{index.stats[:structs]} structs, #{index.stats[:enums]} enums)"
|
|
137
|
-
|
|
138
113
|
port = @options[:port] || 9292
|
|
139
|
-
|
|
140
|
-
|
|
114
|
+
host = @options[:host]
|
|
115
|
+
|
|
116
|
+
app = HTTPServer.build_rack_app(
|
|
117
|
+
root: @options[:root],
|
|
118
|
+
download: @options[:download]
|
|
119
|
+
)
|
|
141
120
|
|
|
142
|
-
warn "Starting
|
|
121
|
+
warn "Starting HTTP server on #{host}:#{port}..."
|
|
143
122
|
warn "Web interface available at http://localhost:#{port}/"
|
|
144
|
-
|
|
123
|
+
warn "MCP endpoint available at http://localhost:#{port}/http"
|
|
124
|
+
Rackup::Server.start(app: app, Host: host, Port: port, Logger: $stderr)
|
|
145
125
|
end
|
|
146
126
|
|
|
147
127
|
def resolve_headers_path
|
data/lib/cf/mcp/server.rb
CHANGED
|
@@ -84,59 +84,93 @@ module CF
|
|
|
84
84
|
transport = ::MCP::Server::Transports::StdioTransport.new(@server)
|
|
85
85
|
transport.open
|
|
86
86
|
end
|
|
87
|
+
end
|
|
87
88
|
|
|
88
|
-
|
|
89
|
-
|
|
89
|
+
# HTTP server with web interface at root and MCP endpoint at /http
|
|
90
|
+
class HTTPServer
|
|
91
|
+
# Build a rack app with automatic header downloading and indexing
|
|
92
|
+
# This is the shared entry point for both config.ru and CLI
|
|
93
|
+
def self.build_rack_app(root: nil, download: false)
|
|
94
|
+
require_relative "parser"
|
|
95
|
+
require_relative "topic_parser"
|
|
96
|
+
require_relative "index"
|
|
97
|
+
require_relative "downloader"
|
|
90
98
|
|
|
91
|
-
|
|
92
|
-
warn "Starting HTTP server on port #{port}..."
|
|
93
|
-
warn "Index contains #{@index.size} items"
|
|
94
|
-
Rackup::Server.start(app: app, Port: port, Logger: $stderr)
|
|
95
|
-
end
|
|
99
|
+
headers_path = resolve_headers_path(root: root, download: download)
|
|
96
100
|
|
|
97
|
-
|
|
98
|
-
|
|
101
|
+
unless File.directory?(headers_path)
|
|
102
|
+
raise "Headers directory not found: #{headers_path}. Use root: or download: true"
|
|
103
|
+
end
|
|
99
104
|
|
|
100
|
-
|
|
101
|
-
|
|
105
|
+
warn "Parsing headers from: #{headers_path}"
|
|
106
|
+
index = build_index(headers_path)
|
|
107
|
+
warn "Indexed #{index.stats[:total]} items (#{index.stats[:functions]} functions, #{index.stats[:structs]} structs, #{index.stats[:enums]} enums)"
|
|
102
108
|
|
|
103
|
-
|
|
109
|
+
new(index).rack_app
|
|
104
110
|
end
|
|
105
111
|
|
|
106
|
-
def
|
|
107
|
-
|
|
108
|
-
|
|
112
|
+
def self.resolve_headers_path(root:, download:)
|
|
113
|
+
return root if root
|
|
114
|
+
return ENV["CF_HEADERS_PATH"] if ENV["CF_HEADERS_PATH"]
|
|
109
115
|
|
|
110
|
-
|
|
111
|
-
|
|
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
|
|
112
123
|
|
|
113
|
-
|
|
114
|
-
warn "Starting SSE server on port #{port}..."
|
|
115
|
-
warn "Index contains #{@index.size} items"
|
|
116
|
-
Rackup::Server.start(app: app, Port: port, Logger: $stderr)
|
|
124
|
+
File.expand_path("~/Work/GitHub/pusewicz/cute_framework/include")
|
|
117
125
|
end
|
|
118
126
|
|
|
119
|
-
|
|
127
|
+
def self.build_index(headers_path)
|
|
128
|
+
parser = Parser.new
|
|
129
|
+
index = Index.new
|
|
120
130
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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}"
|
|
125
144
|
end
|
|
145
|
+
|
|
146
|
+
index
|
|
126
147
|
end
|
|
127
148
|
|
|
128
|
-
def
|
|
129
|
-
|
|
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)
|
|
130
153
|
|
|
131
|
-
|
|
132
|
-
|
|
154
|
+
topics_path = File.join(base, "topics")
|
|
155
|
+
return topics_path if File.directory?(topics_path)
|
|
133
156
|
|
|
134
|
-
|
|
157
|
+
nil
|
|
135
158
|
end
|
|
136
|
-
end
|
|
137
159
|
|
|
138
|
-
|
|
139
|
-
|
|
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
|
+
|
|
140
174
|
def initialize(index)
|
|
141
175
|
@index = index
|
|
142
176
|
end
|
|
@@ -144,22 +178,16 @@ module CF
|
|
|
144
178
|
CORS_HEADERS = {
|
|
145
179
|
"access-control-allow-origin" => "*",
|
|
146
180
|
"access-control-allow-methods" => "GET, POST, DELETE, OPTIONS",
|
|
147
|
-
"access-control-allow-headers" => "Content-Type, Accept, Mcp-Session-Id
|
|
181
|
+
"access-control-allow-headers" => "Content-Type, Accept, Mcp-Session-Id",
|
|
148
182
|
"access-control-expose-headers" => "Mcp-Session-Id"
|
|
149
183
|
}.freeze
|
|
150
184
|
|
|
151
185
|
def rack_app
|
|
152
186
|
require "rack"
|
|
153
187
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
sse_transport = ::MCP::Server::Transports::StreamableHTTPTransport.new(sse_server)
|
|
159
|
-
sse_server.transport = sse_transport
|
|
160
|
-
|
|
161
|
-
http_transport = ::MCP::Server::Transports::StreamableHTTPTransport.new(http_server, stateless: true)
|
|
162
|
-
http_server.transport = http_transport
|
|
188
|
+
mcp_server = create_mcp_server
|
|
189
|
+
http_transport = ::MCP::Server::Transports::StreamableHTTPTransport.new(mcp_server, stateless: true)
|
|
190
|
+
mcp_server.transport = http_transport
|
|
163
191
|
|
|
164
192
|
landing_page = build_landing_page
|
|
165
193
|
index = @index
|
|
@@ -180,17 +208,16 @@ module CF
|
|
|
180
208
|
when %r{^/\.well-known/}
|
|
181
209
|
# OAuth discovery - return 404 to indicate no OAuth required
|
|
182
210
|
[404, {"content-type" => "application/json"}, ['{"error":"Not found"}']]
|
|
183
|
-
when %r{^/sse(/|$)}
|
|
184
|
-
sse_transport.handle_request(request)
|
|
185
211
|
when %r{^/http(/|$)}
|
|
186
212
|
http_transport.handle_request(request)
|
|
187
213
|
else
|
|
188
|
-
# Default route - show landing page for browsers
|
|
214
|
+
# Default route - show landing page for browsers
|
|
189
215
|
accept = request.get_header("HTTP_ACCEPT") || ""
|
|
190
216
|
if request.get? && accept.include?("text/html")
|
|
191
217
|
[200, {"content-type" => "text/html; charset=utf-8"}, [landing_page.call(index, tools)]]
|
|
192
218
|
else
|
|
193
|
-
|
|
219
|
+
# For non-browser clients at root, redirect to /http
|
|
220
|
+
[301, {"location" => "/http", "content-type" => "text/plain"}, ["Redirecting to /http"]]
|
|
194
221
|
end
|
|
195
222
|
end
|
|
196
223
|
|
|
@@ -37,13 +37,9 @@
|
|
|
37
37
|
</div>
|
|
38
38
|
|
|
39
39
|
<h2>Endpoints</h2>
|
|
40
|
-
<div class="endpoint">
|
|
41
|
-
<div class="endpoint-path">/ or /sse</div>
|
|
42
|
-
<div class="endpoint-desc">Streamable HTTP (stateful) - for Claude Desktop and Claude Code</div>
|
|
43
|
-
</div>
|
|
44
40
|
<div class="endpoint">
|
|
45
41
|
<div class="endpoint-path">/http</div>
|
|
46
|
-
<div class="endpoint-desc">
|
|
42
|
+
<div class="endpoint-desc">MCP HTTP endpoint - for Claude Desktop and Claude Code</div>
|
|
47
43
|
</div>
|
|
48
44
|
|
|
49
45
|
<h2>Claude Desktop Setup</h2>
|
|
@@ -53,7 +49,7 @@
|
|
|
53
49
|
<li>Go to <strong>Settings → Connectors</strong></li>
|
|
54
50
|
<li>Add this URL as a remote MCP server:</li>
|
|
55
51
|
</ol>
|
|
56
|
-
<pre><code>https://cf-mcp.fly.dev
|
|
52
|
+
<pre><code>https://cf-mcp.fly.dev/http</code></pre>
|
|
57
53
|
|
|
58
54
|
<h2>Claude Code CLI Setup</h2>
|
|
59
55
|
<pre><code>claude mcp add --transport http cf-mcp https://cf-mcp.fly.dev/http</code></pre>
|
data/lib/cf/mcp/version.rb
CHANGED