一个 Rails 应用程序,两个测功机:在主 SaaS 旁边运行公共 MCP 服务器

诚实地写了 SprintFlint 如何从单个 Rails 8 代码库运行面向用户的 Web 应用程序和公共 MCP 服务器 (mcp.sprintflint.com),通过多进程文件构建包在 Heroku 上部署两次。为什么两个dynos击败一个(blast-radius隔离),为什么一个repo击败两个(无模式漂移,共享身份验证,协调部署),我们在第一次尝试中犯了三个错误(发布运行两次,日志级别,主routes.rb中的MCP路由),以及当这个形状开始吱吱作响时(不同的扩展配置文件,不同的安全态势,不同的语言适合)。

May 05, 2026 · 9 分钟阅读量· SprintFlint团队

2026 年 10 月 3 日更新:SprintFlint 现在在 Railway 上运行。单独的 Web 和 MCP 服务仍然共享一个 Rails 代码库。阅读 我们如何移动服务、数据、上传和备份。下面的文章描述了我们早期的 Heroku 设置。

当我们重新启动公共 MCP 服务器时 mcp.sprintflint.com,来自工程方面的观众自然会提出的问题是: “这是一个单独的代码库吗?” 事实并非如此。 SprintFlint 是一个 Rails 8 应用程序,在 Heroku 上部署了两次;一次作为面向用户的 Web 应用程序,一次作为 MCP 服务器;两者具有相同的代码。这篇文章是工程诚实的版本,说明了我们为什么选择这种形状、多进程文件构建包实际上为您做了什么,以及我们在生产中运行它的前两周观察到的故障模式。

决策树

当您想要将 MCP 服务器与 Rails SaaS 一起交付时,三种可能的形状:

  1. 一个 Rails 应用程序,一个 dyno。 在主应用程序的路由中安装 MCP 服务器。从代码组织的角度来看,爆炸半径最小,但 MCP 代码中的每次内存泄漏都会导致面向用户的应用程序崩溃,并且 MCP 流量的每次峰值都会消耗 Web 请求容量。
  2. 一个 Rails 应用程序,通过多进程文件分离测功机。 相同的代码库,不同的进程。 MCP 流量和 Web 流量落在不同域后面的不同测功机上,但它们共享模型、身份验证、迁移和部署。
  3. 单独的 Rails 应用程序,单独的存储库。 MCP服务器是它自己的服务。最大程度的隔离。最大维护费用;您现在遇到了重复的身份验证代码、模型和迁移协调问题。

大多数 SaaS 初创公司应该选择 (2)。我们做到了。原因如下。

为什么“一个回购,两个动态”是正确的默认设置

当 MCP 服务器位于 SaaS 旁边时,三个属性很重要:

架构是共享的。 MCP 工具列出工单、项目、冲刺。这些与网络应用程序使用的模型相同。在两个存储库中复制它们是两全其美的最糟糕的事情;在工具返回错误数据之前,您不会注意到模式漂移。

身份验证已共享。 相同的不记名令牌适用于 REST 和 MCP。实施两次令牌验证意味着在加强安全性时需要修复两个地方。另外:账户范围、组织成员资格、速率限制分类账;全部单一来源。

部署是协调的。 当您发布模型更改时,Web 应用程序和 MCP 服务器都需要在同一部署窗口中进行更改。拆分存储库设计迫使您考虑跨两个部署的迁移顺序;多进程文件设计只运行一次迁移,两个测功机都会在下次启动时拾取它。

爆炸半径的说法则相反:如果您从与 Web 相同的 dyno 提供 MCP,则 MCP 特定代码中的错误可能会导致 SaaS 瘫痪。一个代码库中的两个动态是一种折衷方案,它可以在运行时为您提供隔离,而无需重复两个存储库。

多进程文件构建包的实际工作原理

Heroku的 多进程文件构建包 是十二行 bash。它读取环境变量(PROCFILE) 在 slug 编译时,复制您指向的文件 Procfile,并让标准 Ruby 构建包接管。

对于我们的设置来说,这意味着:

  • Procfile (默认值;由 sprintflint-product 使用):
    web: bundle exec puma -C config/puma.rb
    worker: bundle exec sidekiq
    release: bundle exec rails db:migrate
    
  • Procfile.mcp (由 sprintflint-mcp 通过使用 PROCFILE=Procfile.mcp):
    web: bundle exec puma -C config/puma.mcp.rb mcp.ru
    release: bundle exec rails db:migrate
    

这两个应用程序都是通过相同的 git Push 构建的。区别在于每个人看到的是哪个 Procfile。 mcp.ru 仅安装 ActionMCP 引擎(无设计、无自动播放、无管理),因此我们获得更小的启动占用空间和不同的路由表面,而无需任何代码重复。

我们第一次尝试时犯了什么错误

三件事,按花费我们时间的顺序排列:

1.将MCP路由放在main中 routes.rb. 我们在第一次部署时就这样做了,因为这是阻力最小的路径。然后意识到 MCP 服务器启动到一个配置,其中包括 Sidekiq Web、设备和整个管理 UI,并且公共 MCP 服务器的路由表面应该恰好是一个挂载点。搬到了一个单独的地方 mcp.ru 仅安装 ActionMCP 引擎的rackup 文件。

2. 忘记这一点 release: 运行两次。 两个 dyno 都编译相同的 slug,因此发布短语在每次部署时运行两次。对于 Rails 来说这基本上没问题; db:migrate 是幂等的;但它会使部署时间加倍。我们本可以删除发布阶段 Procfile.mcp 完全(主应用程序的版本涵盖了迁移),但对称性有助于回滚安全:如果另一个 dyno 损坏,则任何一个 dyno 都可以独立启动。

3. 相同 RAILS_LOG_LEVEL 对于两者。 最初我们有 info 无处不在。 MCP dyno 生成的请求量是 Web dyno 的约 10 倍(小型 JSON-RPC 调用,由始终在线的代理轮询),并且通过日志保留而被消耗。将 MCP 切换为 warn 对于 HTTP 请求,Sidekiq 日志保持详细。每个应用程序的 Heroku 配置干净利落地做到了这一点。

Auth:一个令牌,两个表面

共享的不记名令牌是一种功能,而不是一种气味。这就是我们对此充满信心的原因。

令牌在用户的帐户设置中生成,范围仅限于其组织,并在组织级别受到速率限制(600 请求/分钟)。 REST API 和 MCP 服务器都根据相同的令牌验证相同的令牌 ApiToken 模型。不存在“仅 MCP 代币”或“仅 REST 代币”的概念,因为底层功能是相同的;在用户权限内读取和写入组织数据。

反对统一代币表面的论点:

  • “如果有人受到威胁怎么办?” 两者都同样受到损害,因为他们读取相同的模型。分裂代币只会使表面积增加一倍。
  • “如果我想在不撤销 REST 的情况下撤销 MCP 访问权限怎么办?” 生成两个令牌。撤销一项。代币模型已经支持每个用户多个;我们将它们显示在用户界面中。
  • “那么瞄准镜呢?” 当表面具有明显不同的权限形状时,范围是一个真正值得关注的问题。我们的没有。如果我们稍后向 MCP 提供仅限管理的工具,那就是引入范围界定的时候了;不是今天。

监控:两个仪表板,一个日志形状

两个测功机都交付到同一个原木排水管,但带有 dyno=web.N 和 dyno=mcp_web.N 前缀让我们可以在查询中将它们分开。三个顶级仪表板:

  • dyno 延迟:web p95 的生存时间为 200-400ms,MCP p95 的生存时间为 50-150ms。它们的形状不同; web做ERB渲染,MCP做JSON-RPC + DB查询。
  • 测功机错误率:在部署 Windows 时最有用。如果我们部署 Rails 更改并且只有一个 dyno 的错误率出现峰值,则该错误位于仅其中一个运行的代码中(例如自动播放 UI 或 MCP 引擎)。
  • dyno 达到代币速率限制:告诉我们代理是否在紧密循环中访问 MCP 服务器(有时会这样做; my-issues 每 30 秒轮询一次是一件事)以及稍后是否考虑每个工具的配额。

何时不使用此形状

两个 dyno-one-repo 设计开始出现问题的几种情况:

  • 截然不同的缩放配置文件。 如果 MCP 流量增长速度比网络快 100 倍(有可能;代理在每个人工会话中进行许多小调用),最终部署协调将成为一个约束。拆分为单独的代码库后,MCP 团队可以每天交付,而无需对 Web 流程进行回归测试。
  • 不同的安全态势。 如果 MCP 需要 PCI 合规性而 Web 不需要,那么单独的 VPC/存储库/审计跟踪开始看起来很有吸引力。
  • 不同的语言适合。 Rails 对于今天来说都很好,但如果 MCP 成为高吞吐量流服务,我们可能会用 Go 或 Rust 重写它。这就是这个设计结束的日子。

目前,两个dyno-one-repo 设计正在支付租金。

TL;博士;工程诚实总结

  • 一个 Rails 应用程序、两个 Heroku 应用程序、相同的 git Remote
  • 多进程文件构建包精选 Procfile 或 Procfile.mcp 基于 PROCFILE 环境变量
  • mcp.ru 仅安装 ActionMCP 引擎;更小的引导,更窄的攻击面
  • 共享模型、共享验证、共享部署;不同的运行时,不同的域
  • 在“一个应用程序完成所有工作”(无隔离)和“两个存储库”(您会后悔的重复)之间做出妥协
  • 当 MCP 的扩展速度比 Web 快 100 倍或需要不同的合规性状态时,将重新访问

如果您要在 Rails SaaS 旁边构建公共 MCP 服务器,则可以从这种形状开始。从这里出发。

冲刺 工作流程,无需电子表格蔓延。

SprintFlint 运行您的冲刺,内置速率、容量、燃尽、回顾和预测。每月 50 张免费工单,无需信用卡。