<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/running-a-ruby-mcp-server-in-production-dhayqhwyy" -->

---
title: Running a Ruby MCP Server in Production | daily.dev
description: Covers what it takes to run a Ruby MCP server in production rather than as a local subprocess. Topics include choosing between Streamable HTTP and stdio...
canonical: https://daily.dev/posts/running-a-ruby-mcp-server-in-production-dhayqhwyy
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: Running a Ruby MCP Server in Production | daily.dev
og:description: Covers what it takes to run a Ruby MCP server in production rather than as a local subprocess. Topics include choosing between Streamable HTTP and stdio...
og:url: https://daily.dev/posts/running-a-ruby-mcp-server-in-production-dhayqhwyy
og:image: https://api.daily.dev/og/posts/DHayQHwYy.png
og:image:alt: Running a Ruby MCP Server in Production
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Running a Ruby MCP Server in Production

**[RUBYLAND](https://daily.dev/sources/rubyla)** · 16 min read · 2 upvotes · 1 comments

## Summary

Covers what it takes to run a Ruby MCP server in production rather than as a local subprocess. Topics include choosing between Streamable HTTP and stdio transports, mounting the server standalone vs. inside a Rails app, handling session state with Puma workers (and why stateless mode is now the right default after the 2026-07-28 spec revision), the DNS rebinding security advisory in mcp gem versions before 0.23.0 requiring Host/Origin validation, upgrading from a shared bearer token to OAuth 2.1 with Protected Resource Metadata, scoping data access with read-only models and server_context, and testing with MCP Inspector.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://www.fastruby.io/blog/running-a-ruby-mcp-server-in-production.html>

## Questions this post answers

### What does the mcp gem security advisory GHSA-rjr6-rcgv-9m7m affect and how is it fixed?

Every version of the Ruby mcp gem before 0.23.0 processed incoming JSON-RPC requests over the Streamable HTTP transport without checking the Host or Origin headers, allowing DNS rebinding attacks where a malicious webpage points its hostname at 127.0.0.1 to reach a locally running MCP server and invoke its tools. Version 0.23.0 fixes this by adding allowed_hosts and allowed_origins parameters that must be explicitly configured, since an empty allowlist rejects every request with a 403.

_daily.dev surfaces security advisories like this one for teams running MCP servers in production._

### Why does the mcp gem's StreamableHTTPTransport break when running Puma with multiple workers?

StreamableHTTPTransport stores session and SSE stream state in memory, so it must run in a single process; Puma with workers greater than zero or Unicorn forks processes that do not share memory, breaking session management and open SSE connections. The workaround is passing stateless: true, which drops the session requirement, though the 2026-07-28 MCP specification revision removes protocol-level sessions and the Mcp-Session-Id header entirely, making statelessness the default rather than a workaround.

_daily.dev helps backend engineers track spec changes like this before they deploy MCP infrastructure._

### What must an MCP server implement to act as an OAuth 2.1 resource server?

An MCP server acting as an OAuth 2.1 resource server must implement Protected Resource Metadata per RFC 9728, a JSON document listing which authorization servers can issue tokens for it, with its 401 response pointing to that metadata URL. It must also validate that access tokens were issued specifically for it as the intended audience per RFC 8707, rejecting valid tokens meant for a different service, and return 403 with insufficient_scope when scopes fall short.

_daily.dev keeps developers deciding between auth providers current on MCP's OAuth requirements._

## Community discussion

Top comments from developers on daily.dev.

**@agustinbarrientos** · 0 upvotes

> I'd make the server refuse to boot until it has an allowlist and token validation.

## Similar posts on daily.dev

- [Rails MCP Server 1.5.0: Security Hardening and Sandboxed Environment Support](https://daily.dev/posts/rails-mcp-server-1-5-0-security-hardening-and-sandboxed-environment-support-i5xgkyihw) · Ruby Flow · 0 upvotes · 0 comments
- [Building an MCP Server in .NET Without Buying the Hype — Daily DevOps & .NET](https://daily.dev/posts/building-an-mcp-server-in-net-without-buying-the-hype-daily-devops-net-f3pfe3wik) · Daily DevOps & .NET · 1 upvotes · 0 comments
- [What is a remote MCP server? Local vs. remote MCP explained](https://daily.dev/posts/what-is-a-remote-mcp-server-local-vs-remote-mcp-explained-crhlriquw) · CircleCI · 0 upvotes · 0 comments
- [How To Rev Up Your Rails Development with MCP](https://daily.dev/posts/how-to-rev-up-your-rails-development-with-mcp-ngv87ulfm) · RUBYLAND · 0 upvotes · 0 comments

---

Tags: [#security](https://daily.dev/tags/security), [#ruby](https://daily.dev/tags/ruby), [#rails](https://daily.dev/tags/rails), [#mcp](https://daily.dev/tags/mcp), [#oauth](https://daily.dev/tags/oauth)

[View this post on daily.dev](https://daily.dev/posts/running-a-ruby-mcp-server-in-production-dhayqhwyy)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"Running a Ruby MCP Server in Production","url":"https://daily.dev/posts/running-a-ruby-mcp-server-in-production-dhayqhwyy","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/running-a-ruby-mcp-server-in-production-dhayqhwyy"},"datePublished":"2026-08-04T12:51:48.460Z","dateModified":"2026-09-13T20:56:21.133Z","description":"Covers what it takes to run a Ruby MCP server in production rather than as a local subprocess. Topics include choosing between Streamable HTTP and stdio...","image":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/5398e3b32bb1863916db41e3aa67c7d5?_a=AQAEuop","thumbnailUrl":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/5398e3b32bb1863916db41e3aa67c7d5?_a=AQAEuop","isAccessibleForFree":true,"articleSection":"RUBYLAND","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"RUBYLAND","logo":"https://media.daily.dev/image/upload/t_logo,f_auto/v1/logos/rubyla","url":"https://daily.dev/sources/rubyla"},"commentCount":1,"discussionUrl":"https://daily.dev/posts/running-a-ruby-mcp-server-in-production-dhayqhwyy","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":2},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":1}],"keywords":"security,ruby,rails,mcp,oauth","timeRequired":"PT16M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"RUBYLAND","item":"https://daily.dev/sources/rubyla"},{"@type":"ListItem","position":3,"name":"Running a Ruby MCP Server in Production"}]}
{"@context":"https://schema.org","@type":"WebPage","@id":"https://daily.dev/posts/running-a-ruby-mcp-server-in-production-dhayqhwyy","comment":[{"@type":"Comment","text":"I’d make the server refuse to boot until it has an allowlist and token validation.","datePublished":"2026-08-06T00:10:52.970Z","url":"https://daily.dev/posts/DHayQHwYy#c-FPYrL8OS7","author":{"@type":"Person","name":"Agustin Barrientos","url":"https://daily.dev/agustinbarrientos","image":"https://media.daily.dev/image/upload/s--5ayxQnqn--/f_auto/v1788281802/avatars/avatar_wQYYVe5Tbj0NJ7C7qPoa8?_a=BAMAMicg0"}}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/running-a-ruby-mcp-server-in-production-dhayqhwyy#faq","mainEntity":[{"@type":"Question","name":"What does the mcp gem security advisory GHSA-rjr6-rcgv-9m7m affect and how is it fixed?","acceptedAnswer":{"@type":"Answer","text":"Every version of the Ruby mcp gem before 0.23.0 processed incoming JSON-RPC requests over the Streamable HTTP transport without checking the Host or Origin headers, allowing DNS rebinding attacks where a malicious webpage points its hostname at 127.0.0.1 to reach a locally running MCP server and invoke its tools. Version 0.23.0 fixes this by adding allowed_hosts and allowed_origins parameters that must be explicitly configured, since an empty allowlist rejects every request with a 403. daily.dev surfaces security advisories like this one for teams running MCP servers in production."}},{"@type":"Question","name":"Why does the mcp gem's StreamableHTTPTransport break when running Puma with multiple workers?","acceptedAnswer":{"@type":"Answer","text":"StreamableHTTPTransport stores session and SSE stream state in memory, so it must run in a single process; Puma with workers greater than zero or Unicorn forks processes that do not share memory, breaking session management and open SSE connections. The workaround is passing stateless: true, which drops the session requirement, though the 2026-07-28 MCP specification revision removes protocol-level sessions and the Mcp-Session-Id header entirely, making statelessness the default rather than a workaround. daily.dev helps backend engineers track spec changes like this before they deploy MCP infrastructure."}},{"@type":"Question","name":"What must an MCP server implement to act as an OAuth 2.1 resource server?","acceptedAnswer":{"@type":"Answer","text":"An MCP server acting as an OAuth 2.1 resource server must implement Protected Resource Metadata per RFC 9728, a JSON document listing which authorization servers can issue tokens for it, with its 401 response pointing to that metadata URL. It must also validate that access tokens were issued specifically for it as the intended audience per RFC 8707, rejecting valid tokens meant for a different service, and return 403 with insufficient_scope when scopes fall short. daily.dev keeps developers deciding between auth providers current on MCP's OAuth requirements."}}]}
```

