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 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
@@ -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