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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fa40d95e95c2179eeaa55f22e374232510ee6857aec12ca73ab5c438d9808f16
4
- data.tar.gz: 1047aa59ffc741fb792778037a9e958ee3af42fec332a0653882a351277ef10f
3
+ metadata.gz: 0f3d843ae9e0087f798cad60be61d347da0db7c1f26e5bee5050eccda2a38b29
4
+ data.tar.gz: 98735400ef25d3a51d6d984a0daf12141c1c96b6843922069d71bb24a4183bdc
5
5
  SHA512:
6
- metadata.gz: dac224a9ccf242b04a8d8a801b148f71bf1e9326bf8550560045b2e34ef8501e60f44a7bfd0ea14b06d762c0ddde9b82e50e4896dd2dd7a2489ee823658f9fa3
7
- data.tar.gz: 8ebd60174ed5d853121255db222fb34ee4baa7c033d2ebfdd0fc246d9f5cdfe5cf824a18926e49591c34b052154fdd78623e5b25675bcf4b09521f6107260796
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 three modes of operation:
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 a stateless HTTP server, suitable for simple request/response interactions and multi-node deployments.
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 (stateless)
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
- # Download headers from GitHub if not already present
6
- warn "Initializing CF::MCP server..."
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
- run_server(:http)
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 (stateless)"
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/SSE server (default: 9292 for HTTP, 9393 for SSE)") do |port|
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, :sse, :combined].include?(command)
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 run_combined_server
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
- server = CombinedServer.new(index)
140
- app = server.rack_app
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 combined server on port #{port}..."
121
+ warn "Starting HTTP server on #{host}:#{port}..."
143
122
  warn "Web interface available at http://localhost:#{port}/"
144
- Rackup::Server.start(app: app, Port: port, Logger: $stderr)
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
- def run_http(port: 9292)
89
- require "rackup"
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
- app = http_app
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
- def http_app
98
- require "rack"
101
+ unless File.directory?(headers_path)
102
+ raise "Headers directory not found: #{headers_path}. Use root: or download: true"
103
+ end
99
104
 
100
- transport = ::MCP::Server::Transports::StreamableHTTPTransport.new(@server, stateless: true)
101
- @server.transport = transport
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
- build_rack_app(transport)
109
+ new(index).rack_app
104
110
  end
105
111
 
106
- def run_sse(port: 9393)
107
- require "rack"
108
- require "rackup"
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
- transport = ::MCP::Server::Transports::StreamableHTTPTransport.new(@server)
111
- @server.transport = transport
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
- app = build_rack_app(transport)
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
- private
127
+ def self.build_index(headers_path)
128
+ parser = Parser.new
129
+ index = Index.new
120
130
 
121
- def build_rack_app(transport)
122
- Rack::Builder.new do
123
- use Rack::CommonLogger
124
- run ->(env) { transport.handle_request(Rack::Request.new(env)) }
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 sse_app
129
- require "rack"
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
- transport = ::MCP::Server::Transports::StreamableHTTPTransport.new(@server)
132
- @server.transport = transport
154
+ topics_path = File.join(base, "topics")
155
+ return topics_path if File.directory?(topics_path)
133
156
 
134
- build_rack_app(transport)
157
+ nil
135
158
  end
136
- end
137
159
 
138
- # Combined server that exposes both SSE and HTTP transports under different paths
139
- class CombinedServer
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, Last-Event-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
- # Create separate server instances for each transport
155
- sse_server = create_mcp_server
156
- http_server = create_mcp_server
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, SSE for MCP clients
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
- sse_transport.handle_request(request)
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">Streamable HTTP (stateless) - for simple integrations</div>
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 &rarr; 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/</code></pre>
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>
@@ -2,6 +2,6 @@
2
2
 
3
3
  module CF
4
4
  module MCP
5
- VERSION = "0.10.0"
5
+ VERSION = "0.12.0"
6
6
  end
7
7
  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.10.0
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Usewicz