Cover image
Try Now
2025-04-05

A easy-to-use and minimal server implementation for the Model Context Protocol (MCP) in Ruby.

3 years

Works with Finder

1

Github Watches

0

Github Forks

0

Github Stars

MCPRuby

Gem Version Build Status Maintainability Test Coverage License: MIT

MCPRuby provides server-side tools for implementing the Model Context Protocol (MCP) in Ruby applications. MCP is a specification for how Large Language Models (LLMs) can discover and interact with external tools, resources, and prompts provided by separate applications (MCP Servers).

This library allows you to easily create MCP servers that expose your application's capabilities (like functions, data sources, or predefined prompt templates) to compatible LLM clients (e.g., Claude Desktop App, custom clients).

Features

  • MCP Specification Adherence: Implements core server-side aspects of the MCP specification.
  • Tools: Define and register custom tools (functions) that the LLM can invoke.
  • Resources: Expose data sources (files, database results, API outputs) for the LLM to read.
  • Prompts: Provide structured prompt templates the LLM can request and use.
  • Multiple Transports:
    • Stdio: Simple transport using standard input/output, ideal for process-based servers.
    • SSE (Server-Sent Events): Asynchronous network transport using the modern async/falcon ecosystem.
  • Extensible Handlers: Provides default handlers for core MCP methods, which can be overridden.
  • Clear Error Handling: Custom error classes mapping to JSON-RPC/MCP error codes.
  • Ruby-like API: Uses blocks for registering handlers, following idiomatic Ruby patterns.

Installation

Add this line to your application's Gemfile:

gem 'mcp_ruby'

And then execute:

$ bundle install

Or install it yourself as:

$ gem install mcp_ruby

Note: The SSE transport requires additional gems (async, async-http, falcon, rack). These will be installed automatically if you install mcp_ruby.

Quick Start (Stdio Example)

This example creates a simple server that runs over standard input/output and provides one tool.

#!/usr/bin/env ruby
# frozen_string_literal: true

require 'mcp_ruby'

# Optional: Set log level (DEBUG, INFO, WARN, ERROR, FATAL)
MCPRuby.logger.level = Logger::INFO

# 1. Create a server instance
server = MCPRuby.new_server(name: "MySimpleStdioServer", version: "1.0")

# 2. Register a tool
server.register_tool(
  name: "simple_echo",
  description: "Echoes back the provided message.",
  # Define expected input using JSON Schema format
  input_schema: {
    type: "object",
    properties: {
      message: { type: "string", description: "The message to echo." }
    },
    required: ["message"]
  }
  # The block receives arguments (hash) and the session object
) do |args, _session|
  input = args["message"] || args[:message] # Handle string/symbol keys
  server.logger.info "Echoing: #{input}"
  # The return value will be converted to MCP 'content' format
  "You sent: #{input}"
end

# 3. Run the server using the stdio transport
begin
  server.run(transport: :stdio)
rescue Interrupt
  MCPRuby.logger.info("Server stopped.")
rescue StandardError => e
  MCPRuby.logger.fatal("Server crashed: #{e.message}\n#{e.backtrace.join("\n")}")
  exit 1
end

To run this:

  1. Save it as my_server.rb.

  2. Run ruby my_server.rb.

  3. The server is now listening on stdin/stdout. You can interact with it by sending JSON-RPC messages (see MCP specification):

    • Send Initialize:
      {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"ManualClient","version":"0.1"}}}
      
    • (Server Responds)
    • Send Initialized:
      {"jsonrpc":"2.0","method":"initialized","params":{}}
      
    • List Tools:
      {"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}
      
    • (Server Responds with simple_echo tool definition)
    • Call Tool:
      {"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"simple_echo","arguments":{"message":"Hello MCPRuby!"}}}
      
    • (Server Responds with echo result)

Usage

Creating a Server

Instantiate the server using the factory method:

require 'mcp_ruby'

server = MCPRuby.new_server(
  name: "MyAwesomeServer",
  version: "2.1.0",
  log_level: Logger::DEBUG # Optional: Default is INFO
)

Registering Tools

Tools are functions your server exposes. Use register_tool with a block.

server.register_tool(
  name: "calculate_sum",
  description: "Adds two numbers together.",
  input_schema: {
    type: "object",
    properties: {
      a: { type: "number", description: "First number" },
      b: { type: "number", description: "Second number" }
    },
    required: ["a", "b"]
  }
) do |args, session|
  # args is a hash like { "a" => 10, "b" => 5 }
  # session object provides session context (e.g., session.initialized?)
  sum = (args["a"] || 0) + (args["b"] || 0)
  "The sum is: #{sum}" # Return value is converted to {type: "text", text: ...}
end
  • The input_schema must be a Hash representing a valid JSON Schema object describing the tool's expected arguments.
  • The block receives the arguments hash and the MCPRuby::Session object.
  • The block's return value is automatically converted into the MCP content array format by MCPRuby::Util.convert_to_mcp_content. You can return:
    • A String: Becomes { type: 'text', text: '...' }.
    • A Hash matching the MCP content structure ({ type: 'text', ... }, { type: 'image', ... }, etc.): Used as is.
    • Other Hash objects: JSON-encoded into { type: 'text', text: '...', mimeType: 'application/json' }.
    • Binary String (Encoding::ASCII_8BIT): Base64-encoded into { type: 'blob', blob: '...', mimeType: 'application/octet-stream' }.
    • An Array of the above: Each element is converted and combined.
    • Other objects: Converted using to_s into { type: 'text', text: '...' }.

Registering Resources

Resources provide data that the client can read.

server.register_resource(
  uri: "memory://status", # Unique URI for this resource
  name: "Server Status",
  description: "Provides the current server status.",
  mime_type: "application/json" # Optional: Defaults to text/plain
) do |session|
  # Handler block receives the session object
  {
    status: "OK",
    uptime: Time.now - server_start_time, # Example value
    initialized: session.initialized?
  } # Hash will be JSON encoded due to mime_type
end

# Resource returning binary data
server.register_resource(
  uri: "file://logo.png",
  name: "Logo Image",
  description: "The server's logo.",
  mime_type: "image/png"
) do |session|
  # IMPORTANT: Return binary data as a string with ASCII-8BIT encoding
  File.binread("path/to/logo.png")
end
  • The block receives the MCPRuby::Session object.
  • Return String for text, or a binary String (Encoding::ASCII_8BIT) for binary data. Other types are generally JSON-encoded.

Registering Prompts

Prompts provide templates for the client/LLM.

server.register_prompt(
  name: "summarize_document",
  description: "Creates a prompt to summarize a document.",
  # Define arguments the client can provide
  arguments: [
    { name: "doc_uri", description: "URI of the document resource to summarize.", required: true },
    { name: "length", description: "Desired summary length (e.g., 'short', 'medium', 'long').", required: false }
  ]
) do |args, session|
  # args is a hash like { "doc_uri" => "file://...", "length" => "short" }
  doc_uri = args["doc_uri"]
  length_hint = args["length"] ? " Keep the summary #{args['length']}." : ""

  # Handler must return an array of message hashes
  [
    {
      role: "user",
      content: { type: "text", text: "Please summarize the following document.#{length_hint}" }
    },
    # You might read the resource here to include its content, or let the client do it
    # Example: Reading resource inside prompt handler (use with caution for large files)
    # begin
    #   # Note: read_resource handler itself runs in the server context
    #   # Accessing it directly here might block if not careful.
    #   # A better pattern might be to just reference the URI.
    #   resource_content = server.resources[doc_uri]&.handler&.call(session) || "[Document content not found]"
    #   {
    #     role: "user",
    #     content: { type: "text", text: "Document Content:\n#{resource_content}" }
    #   }
    # rescue => e
    #   { role: "user", content: { type: "text", text: "[Error reading document: #{e.message}]"} }
    # end
    # Example: Referencing the resource URI (preferred)
    {
      role: "user",
      # This assumes the client understands how to fetch and include the resource content
      content: { type: "text", text: "Document to Summarize URI: #{doc_uri}" }
    }
  ]
end
  • The arguments array describes parameters the client can pass when requesting the prompt.
  • The block receives the arguments hash and the session.
  • The block must return an Array of Hashes, where each hash conforms to the MCP PromptMessage structure ({ role: 'user'|'assistant', content: { type: 'text'|'image'|'resource', ... } }).

Running the Server

Use the run method, specifying the desired transport.

Stdio:

server.run(transport: :stdio)

SSE:

Requires the async, falcon, etc. gems.

server.run(
  transport: :sse,
  options: {
    host: "0.0.0.0",        # Default: 'localhost'
    port: 8080,             # Default: 8000
    path_prefix: "/my_mcp"  # Default: '/mcp'. Endpoints become /my_mcp/sse and /my_mcp/message
  }
)

The SSE server uses Falcon and runs asynchronously. Use Ctrl+C (SIGINT) or SIGTERM to stop it gracefully.

Custom Handlers

You can override default handlers or add handlers for non-standard methods:

# Override default ping
server.on_request("ping") do |_params, _session, _server|
  { received_ping_at: Time.now }
end

# Handle a custom notification
server.on_notification("custom/my_event") do |params, session, server|
  server.logger.info "Received my_event: #{params.inspect}"
  # Do something...
end

Architecture

  • MCPRuby::Server: The main class. Manages registration, state, and dispatches incoming messages to appropriate handlers.
  • MCPRuby::Transport::{Stdio, SSE}: Handle the specifics of communication over different channels (stdin/stdout or HTTP SSE). They read raw data, parse JSON, call Server#handle_message, and send back formatted JSON-RPC responses/errors.
  • MCPRuby::Session: Holds state related to a specific client connection, primarily the initialization status and negotiated capabilities. Passed to handlers.
  • MCPRuby::Definitions::{Tool, Resource, Prompt}: Simple structs holding registered capability information and handler blocks.
  • MCPRuby::Handlers::Core: Contains default implementations for standard MCP methods.
  • MCPRuby::Errors: Custom exception classes mapping to JSON-RPC error codes.
  • MCPRuby::Util: Utility functions.

Development

After checking out the repo:

  1. Install dependencies:
    $ bundle install
    
  2. Run tests:
    $ bundle exec rspec
    
  3. Run an example server:
    $ bundle exec ruby examples/stdio_server.rb
    # or
    $ bundle exec ruby examples/simple_server.rb # For SSE
    

You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in lib/mcp_ruby/version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/sergiobayona/mcp_ruby.

License

The gem is available as open source under the terms of the MIT License.

相关推荐

  • https://maiplestudio.com
  • Find Exhibitors, Speakers and more

  • Yusuf Emre Yeşilyurt
  • I find academic articles and books for research and literature reviews.

  • Emmet Halm
  • Converts Figma frames into front-end code for various mobile frameworks.

  • https://suefel.com
  • Latest advice and best practices for custom GPT development.

  • Carlos Ferrin
  • Encuentra películas y series en plataformas de streaming.

  • Joshua Armstrong
  • Confidential guide on numerology and astrology, based of GG33 Public information

  • https://zenepic.net
  • Embark on a thrilling diplomatic quest across a galaxy on the brink of war. Navigate complex politics and alien cultures to forge peace and avert catastrophe in this immersive interstellar adventure.

  • Elijah Ng Shi Yi
  • Advanced software engineer GPT that excels through nailing the basics.

  • https://reddgr.com
  • Delivers concise Python code and interprets non-English comments

  • 林乔安妮
  • A fashion stylist GPT offering outfit suggestions for various scenarios.

  • 1Panel-dev
  • 💬 MaxKB is a ready-to-use AI chatbot that integrates Retrieval-Augmented Generation (RAG) pipelines, supports robust workflows, and provides advanced MCP tool-use capabilities.

  • ShrimpingIt
  • Micropython I2C-based manipulation of the MCP series GPIO expander, derived from Adafruit_MCP230xx

  • Mintplex-Labs
  • The all-in-one Desktop & Docker AI application with built-in RAG, AI agents, No-code agent builder, MCP compatibility, and more.

  • GLips
  • MCP server to provide Figma layout information to AI coding agents like Cursor

  • open-webui
  • User-friendly AI Interface (Supports Ollama, OpenAI API, ...)

  • Dhravya
  • Collection of apple-native tools for the model context protocol.

    Reviews

    4 (1)
    Avatar
    user_h9ImC9ib
    2025-04-18

    MCPRuby is an outstanding tool for anyone working with Ruby, thanks to its seamless integration and robust functionalities. Sergio Bayona has done an excellent job creating a comprehensive resource that enhances productivity and development efficiency. Check out the project on GitHub for detailed information and to get started: https://github.com/sergiobayona/MCPRuby. Highly recommended for Ruby enthusiasts seeking a dependable application!