fast-mcp 0.1.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 +7 -0
- data/CHANGELOG.md +20 -0
- data/LICENSE +21 -0
- data/README.md +321 -0
- data/lib/fast_mcp.rb +69 -0
- data/lib/mcp/logger.rb +33 -0
- data/lib/mcp/resource.rb +158 -0
- data/lib/mcp/server.rb +491 -0
- data/lib/mcp/tool.rb +808 -0
- data/lib/mcp/transports/authenticated_rack_transport.rb +72 -0
- data/lib/mcp/transports/base_transport.rb +40 -0
- data/lib/mcp/transports/rack_transport.rb +468 -0
- data/lib/mcp/transports/stdio_transport.rb +62 -0
- data/lib/mcp/version.rb +6 -0
- metadata +102 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 94b9b8af1c648e0637ad0dc3d0118c8354392bac61b0f8c30d4eb264bc7a8fda
|
4
|
+
data.tar.gz: 728c09e7b7848fcc1f9296e42fed083933b8c8bb9cf08d02c8708009a378f0c1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1e26886cc6c006df64e913b7d517fbcffd1c2c4e6a27787e8fccbb3e0d5c172ec1e337ada9928cff1821fca96e2fcd173e4bf7bc4f492fc9eb4404463ed3e0a8
|
7
|
+
data.tar.gz: c91ea6f439f68ae5f301f4cd5adb24aaebc1939cfe8f2458ec038310b294293b2eaf6b5f8518972fdef5f1bf6acdbda59ec14997d1a01cf46171d4605152cf93
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
|
+
|
8
|
+
## [0.1.0] - 2025-03-12
|
9
|
+
|
10
|
+
### Added
|
11
|
+
|
12
|
+
- Initial release of the Fast MCP library
|
13
|
+
- MCP::Tool class with multiple definition styles
|
14
|
+
- MCP::Server class with STDIO transport and HTTP / SSE transport
|
15
|
+
- Rack Integration with authenticated and standard middleware options
|
16
|
+
- Resource management with subscription capabilities
|
17
|
+
- Binary resource support
|
18
|
+
- Examples with STDIO Transport, HTTP & SSE, Rack app
|
19
|
+
- Initialize lifecycle with capabilities
|
20
|
+
- Comprehensive test suite with RSpec
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 Yorick Jacquin
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,321 @@
|
|
1
|
+
# Fast MCP 🚀
|
2
|
+
|
3
|
+
<div align="center">
|
4
|
+
<h3>Connect AI models to your Ruby applications with ease</h3>
|
5
|
+
<p>No complex protocols, no integration headaches, no compatibility issues – just beautiful, expressive Ruby code.</p>
|
6
|
+
</div>
|
7
|
+
|
8
|
+
<p align="center">
|
9
|
+
<a href="https://badge.fury.io/rb/fast-mcp"><img src="https://badge.fury.io/rb/fast-mcp.svg" alt="Gem Version" /></a>
|
10
|
+
<a href="https://github.com/yjacquin/fast-mcp/workflows/CI/badge.svg"><img src="https://github.com/yjacquin/fast-mcp/workflows/CI/badge.svg" alt="CI Status" /></a>
|
11
|
+
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT" /></a>
|
12
|
+
<a href="code_of_conduct.md"><img src="https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg" alt="Contributor Covenant" /></a>
|
13
|
+
</p>
|
14
|
+
|
15
|
+
## 🌟 Interface your Servers with LLMs in minutes !
|
16
|
+
|
17
|
+
AI models are powerful, but they need to interact with your applications to be truly useful. Traditional approaches mean wrestling with:
|
18
|
+
|
19
|
+
- 🔄 Complex communication protocols and custom JSON formats
|
20
|
+
- 🔌 Integration challenges with different model providers
|
21
|
+
- 🧩 Compatibility issues between your app and AI tools
|
22
|
+
- 🧠 Managing the state between AI interactions and your data
|
23
|
+
|
24
|
+
Fast MCP solves all these problems by providing a clean, Ruby-focused implementation of the [Model Context Protocol](https://github.com/modelcontextprotocol), making AI integration a joy, not a chore.
|
25
|
+
|
26
|
+
## ✨ Features
|
27
|
+
|
28
|
+
- 🛠️ **Tools API** - Let AI models call your Ruby functions securely, with in-depth argument validation through [Dry-Schema](https://github.com/dry-rb/dry-schema).
|
29
|
+
- 📚 **Resources API** - Share data between your app and AI models
|
30
|
+
- 🔄 **Multiple Transports** - Choose from STDIO, HTTP, or SSE based on your needs
|
31
|
+
- 🧩 **Framework Integration** - Works seamlessly with Rails, Sinatra, and Hanami
|
32
|
+
- 🔒 **Authentication Support** - Secure your AI endpoints with ease
|
33
|
+
- 🚀 **Real-time Updates** - Subscribe to changes for interactive applications
|
34
|
+
|
35
|
+
## 💎 What Makes FastMCP Great
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
# Define tools for AI models to use
|
39
|
+
server = MCP::Server.new(name: 'recipe-ai', version: '1.0.0')
|
40
|
+
|
41
|
+
# Define a tool by inheriting from MCP::Tool
|
42
|
+
class GetRecipesTool < MCP::Tool
|
43
|
+
description "Find recipes based on ingredients"
|
44
|
+
|
45
|
+
arguments do
|
46
|
+
# These arguments will generate the needed JSON to be presented to the MCP Client
|
47
|
+
# And they will be vaidated at run time.
|
48
|
+
# The validation is based off Dry-Schema, with the addition of the description.
|
49
|
+
required(:ingredients).array(:string).description("List of ingredients")
|
50
|
+
optional(:cuisine).filled(:string).description("Type of cuisine")
|
51
|
+
end
|
52
|
+
|
53
|
+
def call(ingredients:, cuisine: nil)
|
54
|
+
Recipe.find_by_ingredients(ingredients, cuisine: cuisine)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Register the tool with the server
|
59
|
+
server.register_tool(GetRecipesTool)
|
60
|
+
|
61
|
+
# Share data resources with AI models by inheriting from MCP::Resource
|
62
|
+
class IngredientsResource < MCP::Resource
|
63
|
+
uri "food/popular_ingredients"
|
64
|
+
name "Popular Ingredients"
|
65
|
+
mime_type "application/json"
|
66
|
+
|
67
|
+
def default_content
|
68
|
+
JSON.generate(Ingredient.popular.as_json)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Register the resource with the server
|
73
|
+
server.register_resource(IngredientsResource)
|
74
|
+
|
75
|
+
# Accessing the resource through the server
|
76
|
+
server.read_resource("food/popular_ingredients")
|
77
|
+
|
78
|
+
# Updating the resource content through the server
|
79
|
+
server.update_resource("food/popular_ingredients", JSON.generate({id: 1, name: 'tomato'}))
|
80
|
+
|
81
|
+
|
82
|
+
# Easily integrate with web frameworks
|
83
|
+
# config/application.rb (Rails)
|
84
|
+
config.middleware.use MCP::RackMiddleware.new(
|
85
|
+
name: 'recipe-ai',
|
86
|
+
version: '1.0.0'
|
87
|
+
) do |server|
|
88
|
+
# Register tools and resources here
|
89
|
+
server.register_tool(GetRecipesTool)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Secure your AI endpoints
|
93
|
+
config.middleware.use MCP::AuthenticatedRackMiddleware.new(
|
94
|
+
name: 'recipe-ai',
|
95
|
+
version: '1.0.0',
|
96
|
+
token: ENV['MCP_AUTH_TOKEN']
|
97
|
+
)
|
98
|
+
|
99
|
+
# Build real-time applications
|
100
|
+
server.on_resource_update do |resource|
|
101
|
+
ActionCable.server.broadcast("recipe_updates", resource.metadata)
|
102
|
+
end
|
103
|
+
```
|
104
|
+
|
105
|
+
## 📦 Installation
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
# In your Gemfile
|
109
|
+
gem 'fast-mcp'
|
110
|
+
|
111
|
+
# Then run
|
112
|
+
bundle install
|
113
|
+
|
114
|
+
# Or install it yourself
|
115
|
+
gem install fast-mcp
|
116
|
+
```
|
117
|
+
|
118
|
+
## 🚀 Quick Start
|
119
|
+
|
120
|
+
### Testing with the inspector
|
121
|
+
|
122
|
+
MCP has developed a very [useful inspector](https://github.com/modelcontextprotocol/inspector).
|
123
|
+
You can use it to validate your implementation. I suggest you use the examples I provided with this project as an easy boilerplate.
|
124
|
+
Clone this project, then give it a go !
|
125
|
+
|
126
|
+
```shell
|
127
|
+
npx @modelcontextprotocol/inspector examples/server_with_stdio_transport.rb
|
128
|
+
```
|
129
|
+
Or to test with an SSE transport using a rack middleware:
|
130
|
+
```shell
|
131
|
+
npx @modelcontextprotocol/inspector examples/rack_middleware.rb
|
132
|
+
```
|
133
|
+
|
134
|
+
Or to test over SSE with an authenticated rack middleware:
|
135
|
+
```shell
|
136
|
+
npx @modelcontextprotocol/inspector examples/authenticated_rack_middleware.rb
|
137
|
+
```
|
138
|
+
|
139
|
+
You can test your custom implementation with the official MCP inspector by using:
|
140
|
+
```shell
|
141
|
+
# Test with a stdio transport:
|
142
|
+
npx @modelcontextprotocol/inspector path/to/your_ruby_file.rb
|
143
|
+
|
144
|
+
# Test with an HTTP / SSE server. In the UI select SSE and input your address.
|
145
|
+
npx @modelcontextprotocol/inspector
|
146
|
+
```
|
147
|
+
|
148
|
+
### Create a Server with Tools and Resources
|
149
|
+
|
150
|
+
```ruby
|
151
|
+
require 'fast_mcp'
|
152
|
+
|
153
|
+
# Create an MCP server
|
154
|
+
server = MCP::Server.new(name: 'my-ai-server', version: '1.0.0')
|
155
|
+
|
156
|
+
# Define a tool by inheriting from MCP::Tool
|
157
|
+
class SummarizeTool < MCP::Tool
|
158
|
+
description "Summarize a given text"
|
159
|
+
|
160
|
+
arguments do
|
161
|
+
required(:text).filled(:string).description("Text to summarize")
|
162
|
+
optional(:max_length).filled(:integer).description("Maximum length of summary")
|
163
|
+
end
|
164
|
+
|
165
|
+
def call(text:, max_length: 100)
|
166
|
+
# Your summarization logic here
|
167
|
+
text.split('.').first(3).join('.') + '...'
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# Register the tool with the server
|
172
|
+
server.register_tool(SummarizeTool)
|
173
|
+
|
174
|
+
# Create a resource by inheriting from MCP::Resource
|
175
|
+
class StatisticsResource < MCP::Resource
|
176
|
+
uri "data/statistics"
|
177
|
+
name "Usage Statistics"
|
178
|
+
description "Current system statistics"
|
179
|
+
mime_type "application/json"
|
180
|
+
|
181
|
+
def default_content
|
182
|
+
JSON.generate({
|
183
|
+
users_online: 120,
|
184
|
+
queries_per_minute: 250,
|
185
|
+
popular_topics: ["Ruby", "AI", "WebDev"]
|
186
|
+
})
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# Register the resource with the server
|
191
|
+
server.register_resource(StatisticsResource.new)
|
192
|
+
|
193
|
+
# Start the server
|
194
|
+
server.start
|
195
|
+
```
|
196
|
+
|
197
|
+
### Integrate with Web Frameworks
|
198
|
+
|
199
|
+
#### Rails
|
200
|
+
|
201
|
+
```ruby
|
202
|
+
# config/application.rb
|
203
|
+
module YourApp
|
204
|
+
class Application < Rails::Application
|
205
|
+
# ...
|
206
|
+
config.middleware.use MCP::RackMiddleware.new(
|
207
|
+
name: 'my-ai-server',
|
208
|
+
version: '1.0.0'
|
209
|
+
) do |server|
|
210
|
+
# Register tools and resources here
|
211
|
+
server.register_tool(SummarizeTool)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
```
|
216
|
+
|
217
|
+
#### Sinatra
|
218
|
+
|
219
|
+
```ruby
|
220
|
+
# app.rb
|
221
|
+
require 'sinatra'
|
222
|
+
require 'fast_mcp'
|
223
|
+
|
224
|
+
use MCP::RackMiddleware.new(name: 'my-ai-server', version: '1.0.0') do |server|
|
225
|
+
# Register tools and resources here
|
226
|
+
server.register_tool(SummarizeTool)
|
227
|
+
end
|
228
|
+
|
229
|
+
get '/' do
|
230
|
+
'Hello World!'
|
231
|
+
end
|
232
|
+
```
|
233
|
+
|
234
|
+
### Integrating with Claude Desktop
|
235
|
+
|
236
|
+
Add your server to your Claude Desktop configuration at:
|
237
|
+
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
238
|
+
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
|
239
|
+
|
240
|
+
```json
|
241
|
+
{
|
242
|
+
"mcpServers": {
|
243
|
+
"my-great-server": {
|
244
|
+
"command": "ruby",
|
245
|
+
"args": [
|
246
|
+
"/Users/path/to/your/awesome/fast-mcp/server.rb"
|
247
|
+
]
|
248
|
+
}
|
249
|
+
}
|
250
|
+
}
|
251
|
+
```
|
252
|
+
|
253
|
+
## 📊 Supported Specifications
|
254
|
+
|
255
|
+
| Feature | Status |
|
256
|
+
|---------|--------|
|
257
|
+
| ✅ **JSON-RPC 2.0** | Full implementation for communication |
|
258
|
+
| ✅ **Tool Definition & Calling** | Define and call tools with rich argument types |
|
259
|
+
| ✅ **Resource Management** | Create, read, update, and subscribe to resources |
|
260
|
+
| ✅ **Transport Options** | STDIO, HTTP, and SSE for flexible integration |
|
261
|
+
| ✅ **Framework Integration** | Rails, Sinatra, Hanami, and any Rack-compatible framework |
|
262
|
+
| ✅ **Authentication** | Secure your AI endpoints with token authentication |
|
263
|
+
| ✅ **Schema Support** | Full JSON Schema for tool arguments with validation |
|
264
|
+
|
265
|
+
## 🗺️ Use Cases
|
266
|
+
|
267
|
+
- 🤖 **AI-powered Applications**: Connect LLMs to your Ruby app's functionality
|
268
|
+
- 📊 **Real-time Dashboards**: Build dashboards with live AI-generated insights
|
269
|
+
- 🔗 **Microservice Communication**: Use MCP as a clean protocol between services
|
270
|
+
- 📚 **Interactive Documentation**: Create AI-enhanced API documentation
|
271
|
+
- 💬 **Chatbots and Assistants**: Build AI assistants with access to your app's data
|
272
|
+
|
273
|
+
## 📖 Documentation
|
274
|
+
|
275
|
+
- [🚀 Getting Started Guide](docs/getting_started.md)
|
276
|
+
- [🧩 Integration Guide](docs/integration_guide.md)
|
277
|
+
- [🛤️ Rails Integration](docs/rails_integration.md)
|
278
|
+
- [🌐 Sinatra Integration](docs/sinatra_integration.md)
|
279
|
+
- [🌸 Hanami Integration](docs/hanami_integration.md)
|
280
|
+
- [📚 Resources](docs/resources.md)
|
281
|
+
- [🛠️ Tools](docs/tools.md)
|
282
|
+
- [🔌 Transports](docs/transports.md)
|
283
|
+
- [📘 API Reference](docs/api_reference.md)
|
284
|
+
|
285
|
+
## 💻 Examples
|
286
|
+
|
287
|
+
Check out the [examples directory](examples) for more detailed examples:
|
288
|
+
|
289
|
+
- **🔨 Basic Examples**:
|
290
|
+
- [Simple Server](examples/server_with_stdio_transport.rb)
|
291
|
+
- [Tool Examples](examples/tool_examples.rb)
|
292
|
+
|
293
|
+
- **🌐 Web Integration**:
|
294
|
+
- [Rack Middleware](examples/rack_middleware.rb)
|
295
|
+
- [Authenticated Endpoints](examples/authenticated_rack_middleware.rb)
|
296
|
+
|
297
|
+
## 🧪 Requirements
|
298
|
+
|
299
|
+
- Ruby 3.2+
|
300
|
+
|
301
|
+
## 👥 Contributing
|
302
|
+
|
303
|
+
We welcome contributions to Fast MCP! Here's how you can help:
|
304
|
+
|
305
|
+
1. Fork the repository
|
306
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
307
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
308
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
309
|
+
5. Create a new Pull Request
|
310
|
+
|
311
|
+
Please read our [Contributing Guide](CONTRIBUTING.md) for more details.
|
312
|
+
|
313
|
+
## 📄 License
|
314
|
+
|
315
|
+
This project is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
316
|
+
|
317
|
+
## 🙏 Acknowledgments
|
318
|
+
|
319
|
+
- The [Model Context Protocol](https://github.com/modelcontextprotocol) team for creating the specification
|
320
|
+
- The [Dry-Schema](https://github.com/dry-rb/dry-schema) team for the argument validation.
|
321
|
+
- All contributors to this project
|
data/lib/fast_mcp.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Fast MCP - A Ruby Implementation of the Model Context Protocol (Server-side)
|
4
|
+
# https://modelcontextprotocol.io/introduction
|
5
|
+
|
6
|
+
# Define the MCP module
|
7
|
+
module MCP
|
8
|
+
end
|
9
|
+
|
10
|
+
# Require the core components
|
11
|
+
require_relative 'mcp/tool'
|
12
|
+
require_relative 'mcp/server'
|
13
|
+
require_relative 'mcp/resource'
|
14
|
+
|
15
|
+
# Require all transport files
|
16
|
+
require_relative 'mcp/transports/base_transport'
|
17
|
+
Dir[File.join(File.dirname(__FILE__), 'mcp/transports', '*.rb')].each do |file|
|
18
|
+
require file
|
19
|
+
end
|
20
|
+
|
21
|
+
# Version information
|
22
|
+
require_relative 'mcp/version'
|
23
|
+
|
24
|
+
# Convenience method to create a Rack middleware
|
25
|
+
module MCP
|
26
|
+
# Create a Rack middleware for the MCP server
|
27
|
+
# @param app [#call] The Rack application
|
28
|
+
# @param options [Hash] Options for the middleware
|
29
|
+
# @option options [String] :name The name of the server
|
30
|
+
# @option options [String] :version The version of the server
|
31
|
+
# @option options [String] :path_prefix The path prefix for the MCP endpoints
|
32
|
+
# @option options [Logger] :logger The logger to use
|
33
|
+
# @yield [server] A block to configure the server
|
34
|
+
# @yieldparam server [MCP::Server] The server to configure
|
35
|
+
# @return [#call] The Rack middleware
|
36
|
+
def self.rack_middleware(app, options = {})
|
37
|
+
name = options.delete(:name) || 'mcp-server'
|
38
|
+
version = options.delete(:version) || '1.0.0'
|
39
|
+
logger = options.delete(:logger) || Logger.new
|
40
|
+
|
41
|
+
server = MCP::Server.new(name: name, version: version, logger: logger)
|
42
|
+
yield server if block_given?
|
43
|
+
|
44
|
+
# Store the server in the Sinatra settings if available
|
45
|
+
app.settings.set(:mcp_server, server) if app.respond_to?(:settings) && app.settings.respond_to?(:mcp_server=)
|
46
|
+
|
47
|
+
server.start_rack(app, options)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Create a Rack middleware for the MCP server with authentication
|
51
|
+
# @param app [#call] The Rack application
|
52
|
+
# @param options [Hash] Options for the middleware
|
53
|
+
# @option options [String] :name The name of the server
|
54
|
+
# @option options [String] :version The version of the server
|
55
|
+
# @option options [String] :auth_token The authentication token
|
56
|
+
# @yield [server] A block to configure the server
|
57
|
+
# @yieldparam server [MCP::Server] The server to configure
|
58
|
+
# @return [#call] The Rack middleware
|
59
|
+
def self.authenticated_rack_middleware(app, options = {})
|
60
|
+
name = options.delete(:name) || 'mcp-server'
|
61
|
+
version = options.delete(:version) || '1.0.0'
|
62
|
+
logger = options.delete(:logger) || Logger.new
|
63
|
+
|
64
|
+
server = MCP::Server.new(name: name, version: version, logger: logger)
|
65
|
+
yield server if block_given?
|
66
|
+
|
67
|
+
server.start_authenticated_rack(app, options)
|
68
|
+
end
|
69
|
+
end
|
data/lib/mcp/logger.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This class is not used yet.
|
4
|
+
module MCP
|
5
|
+
class Logger < Logger
|
6
|
+
def initialize
|
7
|
+
@client_initialized = false
|
8
|
+
@transport = nil
|
9
|
+
|
10
|
+
super($stdout)
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_accessor :transport, :client_initialized
|
14
|
+
|
15
|
+
def client_initialized?
|
16
|
+
client_initialized
|
17
|
+
end
|
18
|
+
|
19
|
+
def stdio_transport?
|
20
|
+
transport == :stdio
|
21
|
+
end
|
22
|
+
|
23
|
+
def rack_transport?
|
24
|
+
transport == :rack
|
25
|
+
end
|
26
|
+
|
27
|
+
# def add(severity, message = nil, progname = nil, &block)
|
28
|
+
# # return unless client_initialized? && rack_transport?
|
29
|
+
|
30
|
+
# super
|
31
|
+
# end
|
32
|
+
end
|
33
|
+
end
|
data/lib/mcp/resource.rb
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'base64'
|
5
|
+
require 'mime/types'
|
6
|
+
require 'singleton'
|
7
|
+
|
8
|
+
module MCP
|
9
|
+
# Resource class for MCP Resources feature
|
10
|
+
# Represents a resource that can be exposed to clients
|
11
|
+
class Resource
|
12
|
+
class << self
|
13
|
+
# Define URI for this resource
|
14
|
+
# @param value [String, nil] The URI for this resource
|
15
|
+
# @return [String] The URI for this resource
|
16
|
+
def uri(value = nil)
|
17
|
+
@uri = value if value
|
18
|
+
@uri || (superclass.respond_to?(:uri) ? superclass.uri : nil)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Define name for this resource
|
22
|
+
# @param value [String, nil] The name for this resource
|
23
|
+
# @return [String] The name for this resource
|
24
|
+
def resource_name(value = nil)
|
25
|
+
@name = value if value
|
26
|
+
@name || (superclass.respond_to?(:resource_name) ? superclass.resource_name : nil)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Define description for this resource
|
30
|
+
# @param value [String, nil] The description for this resource
|
31
|
+
# @return [String] The description for this resource
|
32
|
+
def description(value = nil)
|
33
|
+
@description = value if value
|
34
|
+
@description || (superclass.respond_to?(:description) ? superclass.description : nil)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Define MIME type for this resource
|
38
|
+
# @param value [String, nil] The MIME type for this resource
|
39
|
+
# @return [String] The MIME type for this resource
|
40
|
+
def mime_type(value = nil)
|
41
|
+
@mime_type = value if value
|
42
|
+
@mime_type || (superclass.respond_to?(:mime_type) ? superclass.mime_type : nil)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Get the resource metadata (without content)
|
46
|
+
# @return [Hash] Resource metadata
|
47
|
+
def metadata
|
48
|
+
{
|
49
|
+
uri: uri,
|
50
|
+
name: resource_name,
|
51
|
+
description: description,
|
52
|
+
mimeType: mime_type
|
53
|
+
}.compact
|
54
|
+
end
|
55
|
+
|
56
|
+
# Load content from a file (class method)
|
57
|
+
# @param file_path [String] Path to the file
|
58
|
+
# @return [Resource] New resource instance with content loaded from file
|
59
|
+
def from_file(file_path, name: nil, description: nil)
|
60
|
+
file_uri = "file://#{File.absolute_path(file_path)}"
|
61
|
+
file_name = name || File.basename(file_path)
|
62
|
+
|
63
|
+
# Create a resource subclass on the fly
|
64
|
+
Class.new(self) do
|
65
|
+
uri file_uri
|
66
|
+
resource_name file_name
|
67
|
+
description description if description
|
68
|
+
|
69
|
+
# Auto-detect mime type
|
70
|
+
extension = File.extname(file_path)
|
71
|
+
unless extension.empty?
|
72
|
+
detected_types = MIME::Types.type_for(extension)
|
73
|
+
mime_type detected_types.first.to_s unless detected_types.empty?
|
74
|
+
end
|
75
|
+
|
76
|
+
# Override content method to load from file
|
77
|
+
define_method :default_content do
|
78
|
+
if binary?
|
79
|
+
File.binread(file_path)
|
80
|
+
else
|
81
|
+
File.read(file_path)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
include Singleton
|
89
|
+
|
90
|
+
attr_accessor :content
|
91
|
+
|
92
|
+
# Initialize a resource singleton instance
|
93
|
+
def initialize
|
94
|
+
@content = default_content
|
95
|
+
end
|
96
|
+
|
97
|
+
# URI of the resource - delegates to class method
|
98
|
+
# @return [String, nil] The URI for this resource
|
99
|
+
def uri
|
100
|
+
self.class.uri
|
101
|
+
end
|
102
|
+
|
103
|
+
# Name of the resource - delegates to class method
|
104
|
+
# @return [String, nil] The name for this resource
|
105
|
+
def name
|
106
|
+
self.class.name
|
107
|
+
end
|
108
|
+
|
109
|
+
# Description of the resource - delegates to class method
|
110
|
+
# @return [String, nil] The description for this resource
|
111
|
+
def description
|
112
|
+
self.class.description
|
113
|
+
end
|
114
|
+
|
115
|
+
# MIME type of the resource - delegates to class method
|
116
|
+
# @return [String, nil] The MIME type for this resource
|
117
|
+
def mime_type
|
118
|
+
self.class.mime_type
|
119
|
+
end
|
120
|
+
|
121
|
+
# Method to be overridden by subclasses to dynamically generate content
|
122
|
+
# @return [String, nil] Generated content for this resource
|
123
|
+
def default_content
|
124
|
+
raise NotImplementedError, 'Subclasses must implement content'
|
125
|
+
end
|
126
|
+
|
127
|
+
# Check if the resource is binary
|
128
|
+
# @return [Boolean] true if the resource is binary, false otherwise
|
129
|
+
def binary?
|
130
|
+
return false if mime_type.nil?
|
131
|
+
|
132
|
+
!(mime_type.start_with?('text/') ||
|
133
|
+
mime_type == 'application/json' ||
|
134
|
+
mime_type == 'application/xml' ||
|
135
|
+
mime_type == 'application/javascript')
|
136
|
+
end
|
137
|
+
|
138
|
+
# Get the resource contents
|
139
|
+
# @return [Hash] Resource contents
|
140
|
+
def contents
|
141
|
+
result = {
|
142
|
+
uri: uri,
|
143
|
+
mimeType: mime_type
|
144
|
+
}
|
145
|
+
|
146
|
+
content_value = content
|
147
|
+
if content_value
|
148
|
+
if binary?
|
149
|
+
result[:blob] = Base64.strict_encode64(content_value)
|
150
|
+
else
|
151
|
+
result[:text] = content_value
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
result
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|