Add Rails example app
## Motivation and Context The README.md documents the "Rails (mount)" integration pattern, but the repository had no runnable Rails example. This adds a minimal Rails application under examples/rails that mounts StreamableHTTPTransport at /mcp and demonstrates class-based tools and a resource with a read handler. The MCP server is built in config/routes.rb rather than in an initializer because Zeitwerk sets up autoloading after initializers run, so the tool classes in app/tools cannot be referenced there. Code reloading is disabled because the transport holds the server built at boot. The app is booted with puma instead of rackup because rackup inserts `Rack::Lint` in its development default, which rejects the capitalized response header names that the transport currently emits. Closes #44. ## How Has This Been Tested? The curl walkthrough in examples/rails/README.md has been verified end to end against the running app: initialize and the initialized notification, tools/list, tools/call for both tools, resources/list, resources/read, the standalone SSE stream, and session termination via DELETE. ## Breaking Changes None. This only adds an example application and documentation links.
This commit is contained in:
parent
8b1a41aedd
commit
16a6df340c
3 changed files with 275 additions and 0 deletions
27
examples/rails/config/application.rb
Normal file
27
examples/rails/config/application.rb
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
||||
|
||||
require "bundler/setup"
|
||||
|
||||
require "rails"
|
||||
require "action_controller/railtie"
|
||||
|
||||
require "mcp"
|
||||
|
||||
module McpRailsExample
|
||||
class Application < Rails::Application
|
||||
config.load_defaults(8.0)
|
||||
|
||||
# This example has no views, assets, or database.
|
||||
config.api_only = true
|
||||
|
||||
# The MCP server is built once at boot (config/routes.rb) and holds references to the tool classes in app/tools,
|
||||
# so code reloading is disabled; restart the server after changing tool or resource code.
|
||||
# A reloading app would need `config.to_prepare` and a way to swap the transport's server instead.
|
||||
config.enable_reloading = false
|
||||
config.eager_load = true
|
||||
|
||||
config.logger = ActiveSupport::Logger.new($stdout)
|
||||
end
|
||||
end
|
||||
5
examples/rails/config/environment.rb
Normal file
5
examples/rails/config/environment.rb
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative "application"
|
||||
|
||||
Rails.application.initialize!
|
||||
46
examples/rails/config/routes.rb
Normal file
46
examples/rails/config/routes.rb
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# The MCP server and its Streamable HTTP transport are built here, once at boot,
|
||||
# following the "Rails (mount)" pattern from the top-level README.
|
||||
# Routes are loaded after the application initializes, so the tool classes in app/tools
|
||||
# are available at this point (they are not yet available in config/initializers,
|
||||
# where Zeitwerk has not set up autoloading).
|
||||
#
|
||||
# The transport keeps session and SSE state in memory, so run a single server process
|
||||
# (e.g., Puma with `workers 0`, which is the rackup default).
|
||||
# See the top-level README for multi-instance and stateless deployment notes.
|
||||
server = MCP::Server.new(
|
||||
name: "rails_example_server",
|
||||
title: "Rails Example Server",
|
||||
version: "1.0.0",
|
||||
tools: [AddTool, GreetingTool],
|
||||
resources: [
|
||||
MCP::Resource.new(
|
||||
uri: "example://rails/readme",
|
||||
name: "readme",
|
||||
title: "Example README",
|
||||
description: "Describes this Rails example MCP server",
|
||||
mime_type: "text/plain",
|
||||
),
|
||||
],
|
||||
server_context: { app_name: "mcp_rails_example" },
|
||||
)
|
||||
|
||||
server.resources_read_handler do |params|
|
||||
[
|
||||
{
|
||||
uri: params[:uri],
|
||||
mimeType: "text/plain",
|
||||
text: "This resource is served by the Rails example MCP server (Rails #{Rails.version}).",
|
||||
},
|
||||
]
|
||||
end
|
||||
|
||||
transport = MCP::Server::Transports::StreamableHTTPTransport.new(server)
|
||||
|
||||
Rails.application.routes.draw do
|
||||
# `StreamableHTTPTransport` is a Rack app, so it can be mounted directly.
|
||||
# `mount` directs all HTTP methods on /mcp to the transport, which internally dispatches POST (JSON-RPC messages),
|
||||
# GET (SSE stream), and DELETE (session termination) per the MCP Streamable HTTP transport spec.
|
||||
mount(transport => "/mcp")
|
||||
end
|
||||
Loading…
Reference in a new issue