Aktualizacja, 3 października 2026 r.: SprintFlint działa teraz na Railway. Oddzielne usługi internetowe i MCP nadal korzystają z tej samej bazy kodu Rails. Przeczytaj jak przenieśliśmy usługi, dane, przesyłane pliki i kopie zapasowe. Poniższy artykuł opisuje naszą wcześniejszą konfigurację Heroku.
Kiedy ponownie uruchomiliśmy publiczny serwer MCP pod adresem mcp.sprintflint.com, naturalnym pytaniem ze strony inżynierów naszej publiczności było: „Czy to osobna baza kodu?” Tak nie jest. SprintFlint to jedna aplikacja Rails 8, wdrożona dwukrotnie na Heroku; raz jako aplikacja internetowa przeznaczona dla użytkownika, raz jako serwer MCP; w obu przypadkach z tym samym kodem. Ten post jest rzetelną inżynierską wersją tego, dlaczego wybraliśmy ten kształt, co tak naprawdę robi dla Ciebie wieloprocowy pakiet kompilacji oraz tryby awarii, które zaobserwowaliśmy w ciągu pierwszych dwóch tygodni uruchomienia go w środowisku produkcyjnym.
Drzewo decyzyjne
Trzy prawdopodobne kształty, gdy chcesz wysłać serwer MCP obok Rails SaaS:
- Jedna aplikacja Rails, jedna hamownia. Zamontuj serwer MCP w trasach głównej aplikacji. Najmniejszy promień wybuchu z punktu widzenia organizacji kodu, ale każdy wyciek pamięci w kodzie MCP powoduje awarię aplikacji dostępnej dla użytkownika, a każdy wzrost ruchu MCP pochłania pojemność żądań sieciowych.
- Jedna aplikacja Railsowa, oddzielne dynos poprzez wiele plików proc. Ta sama baza kodu, oddzielne procesy. Ruch MCP i ruch internetowy odbywają się na różnych hamowniach za różnymi domenami, ale dzielą modele, autoryzację, migracje i wdrożenia.
- Oddzielna aplikacja Railsowa, osobne repozytorium. Serwer MCP jest własną usługą. Maksymalna izolacja. Maksymalne koszty utrzymania; masz teraz zduplikowany kod uwierzytelniający, modele i problemy z koordynacją migracji.
Większość startupów SaaS powinna wybrać (2). Zrobiliśmy. Oto dlaczego.
Dlaczego „jedno repozytorium, dwie dyno” jest właściwym ustawieniem domyślnym
Trzy właściwości mają znaczenie, gdy serwer MCP znajduje się obok SaaS:
Schemat jest udostępniony. Narzędzia MCP wyświetlają listę problemów, projektów i sprintów. Są to te same modele, z których korzysta aplikacja internetowa. Powielanie ich w dwóch repozytoriach jest najgorszym z obu światów; dryftu schematu nie zauważysz, dopóki narzędzie nie zwróci błędnych danych.
Autoryzacja jest wspólna. Ten sam token okaziciela działa w REST i MCP. Dwukrotne wdrożenie weryfikacji tokena oznacza dwa miejsca do naprawienia w przypadku zwiększenia bezpieczeństwa. Także: zakres kont, członkostwo w organizacjach, księgi limitów stawek; wszystko z jednego źródła.
Wdrożenie jest skoordynowane. Kiedy wysyłasz zmianę modelu, zarówno aplikacja internetowa, jak i serwer MCP potrzebują jej w tym samym oknie wdrażania. Projekt podzielonego repo zmusza Cię do zastanowienia się nad kolejnością migracji w dwóch wdrożeniach; projekt z wieloma plikami proc po prostu uruchamia migrację raz i obie dyno pobierają ją przy następnym uruchomieniu.
Argument promienia wybuchu działa w drugą stronę: jeśli obsługujesz MCP z tej samej hamowni co sieć, błąd w kodzie specyficznym dla MCP może uniemożliwić SaaS. Dwie dyno z jednej bazy kodu to kompromis zapewniający izolację w czasie wykonywania bez duplikowania dwóch repozytoriów.
Jak faktycznie działa pakiet kompilacji obsługujący wiele plików
Heroku pakiet kompilacji z wieloma profilami to dwanaście linii basha. Odczytuje zmienną środowiskową (PROCFILE) podczas kompilacji ślimaka kopiuje plik, na który go wskazujesz Procfilei pozwala przejąć kontrolę standardowemu pakietowi kompilacji Ruby.
Dla naszej konfiguracji oznacza to:
-
Procfile(domyślnie; używane przez produkcję sprintflint):web: bundle exec puma -C config/puma.rb worker: bundle exec sidekiq release: bundle exec rails db:migrate -
Procfile.mcp(używany przez sprintflint-mcp przezPROCFILE=Procfile.mcp):web: bundle exec puma -C config/puma.mcp.rb mcp.ru release: bundle exec rails db:migrate
Obie aplikacje są zbudowane na podstawie tego samego git push. Różnica polega na tym, który plik Procfile widzi każdy z nich. mcp.ru montuje tylko silnik ActionMCP (bez tworzenia, bez Autoplay, bez administratora), więc uzyskujemy mniejszy ślad rozruchowy i inną powierzchnię routingu bez powielania kodu.
Co nam się pomyliło przy pierwszej próbie
Trzy rzeczy, według tego, ile czasu nas to kosztowało:
1. Umieszczenie tras MCP w głównym routes.rb. Zrobiliśmy to przy pierwszym rozmieszczeniu, ponieważ była to ścieżka najmniejszego oporu. Następnie zdałem sobie sprawę, że serwer MCP uruchamia się w konfiguracji obejmującej sieć Sidekiq, urządzenie i cały interfejs administratora oraz że powierzchnia routingu dla publicznego serwera MCP powinna wynosić dokładnie jeden punkt podłączenia. Przeniesiony do osobnego mcp.ru rackup, który montuje tylko silnik ActionMCP.
2. Zapomnij o tym release: biegnie dwa razy. Obie dyno kompilują ten sam ślimak, więc fraza wydania działała dwa razy przy każdym wdrożeniu. W przypadku Railsów jest to w większości w porządku; db:migrate jest idempotentny; ale podwaja czas wdrażania. Mogliśmy usunąć fazę uwalniania Procfile.mcp całkowicie (wersja głównej aplikacji obejmuje migrację), ale symetria pomaga w bezpieczeństwie wycofywania zmian: każda hamownia może uruchomić się niezależnie, jeśli druga jest uszkodzona.
3. To samo RAILS_LOG_LEVEL dla obu. Początkowo mieliśmy info wszędzie. Hamownia MCP generuje ~10 razy większą liczbę żądań niż hamownia internetowa (małe wywołania JSON-RPC, odpytywane przez zawsze aktywnych agentów) i przepalała przechowywanie dzienników. Przełączono MCP na warn w przypadku żądań HTTP dzienniki Sidekiq były pełne. Konfiguracja Heroku dla poszczególnych aplikacji zrobiła to czysto.
Auth: jeden token, dwie powierzchnie
Współdzielony token okaziciela to funkcja, a nie zapach. Oto dlaczego jesteśmy tego pewni.
Token jest generowany w ustawieniach konta użytkownika, ograniczony do jego organizacji i ograniczony stawką na poziomie organizacji (600 żądań/min). Zarówno serwer REST API, jak i serwer MCP weryfikują ten sam token względem tego samego ApiToken modelka. Nie ma koncepcji „tokena przeznaczonego wyłącznie do MCP” ani „tokenu przeznaczonego wyłącznie do REST”, ponieważ podstawowe możliwości są takie same; odczytywać i zapisywać dane organizacji w ramach uprawnień użytkownika.
Argumenty przeciwko ujednoliconej powierzchni tokena:
- „A co, jeśli ktoś zostanie skompromitowany?” Oba są równie zagrożone, ponieważ czytają ten sam model. Dzielenie żetonów po prostu podwaja powierzchnię.
- „Co się stanie, jeśli chcę odebrać dostęp MCP bez odwoływania REST?” Wygeneruj dwa tokeny. Odwołaj jedno. Model tokena obsługuje już wiele tokenów na użytkownika; wyświetlamy je w interfejsie użytkownika.
- „A co z lunetami?” Zakresy stanowią poważny problem, gdy powierzchnie mają znacząco różne kształty uprawnień. Nasze nie. Jeśli później udostępnimy MCP narzędzia przeznaczone tylko dla administratorów, będzie to czas na wprowadzenie określania zakresu; niedzisiejszy.
Monitorowanie: dwa dashboardy, jeden kształt kłody
Obie hamownie są wysyłane do tego samego drenażu kłód, ale z dyno=web.N i dyno=mcp_web.N przedrostki, które pozwalają nam je oddzielić w zapytaniach. Trzy dashboardy najwyższego poziomu:
- Opóźnienie przez hamownię: web p95 żyje w 200-400 ms, MCP p95 żyje w 50-150 ms. Mają różne kształty; web renderuje ERB, MCP wykonuje zapytanie JSON-RPC + DB.
- Poziom błędów według hamowni: najbardziej przydatny przy wdrażaniu systemu Windows. Jeśli wdrożymy zmianę w Railsach i poziom błędów tylko jednej hamowni wzrośnie, błąd tkwi w kodzie, na którym działa tylko jedna z nich (np. interfejs Autoplay lub silnik MCP).
-
Tokenowe trafienia z limitem szybkości przez hamownię: mówi nam, czy agenci uderzają w serwer MCP w ciasnych pętlach (czasami to robią;
my-issuesodpytywanie co 30 sekund jest rzeczą ważną) i czy później rozważyć przydziały na narzędzie.
Kiedy NIE używać tego kształtu
Kilka przypadków, w których projekt dwóch dyno-jednego repo zaczyna skrzypieć:
- Bardzo różne profile skalowania. Jeśli ruch MCP będzie rósł 100 razy szybciej niż w sieci (może tak być; agenci wykonują wiele małych wywołań na sesję), w końcu koordynacja wdrażania stanie się ograniczeniem. Podział na oddzielną bazę kodu umożliwia zespołowi MCP codzienną wysyłkę bez testowania regresyjnego przepływu sieci.
- Inna postawa bezpieczeństwa. Jeśli MCP wymaga zgodności z PCI, a sieć nie, osobna ścieżka VPC/repo/audytu zaczyna wyglądać atrakcyjnie.
- Pasuje inny język. Railsy są dziś w porządku w obu przypadkach, ale jeśli MCP stanie się usługą przesyłania strumieniowego o dużej przepustowości, być może przepiszemy ją w Go lub Rust. To dzień, w którym ten projekt się kończy.
Na razie projekt dwa-dyno-jedno-repo płaci czynsz.
TL;DR; uczciwe inżyniersko podsumowanie
- Jedna aplikacja Railsowa, dwie aplikacje Heroku, ten sam pilot Git
- Wybór pakietów kompilacji obejmujących wiele profili
ProcfilelubProcfile.mcpoparty naPROCFILEśrodowisko var -
mcp.rumontuje tylko silnik ActionMCP; mniejszy but, węższa powierzchnia ataku - Udostępnione modele, wspólne uwierzytelnianie, wspólne wdrożenia; inny czas wykonania, inna domena
- Kompromis pomiędzy „jedną aplikacją do wszystkiego” (brak izolacji) a „dwoma repozytoriami” (powielania, którego będziesz żałować)
- Odwiedzimy ponownie, gdy MCP będzie skalować się 100 razy szybciej niż sieć lub będzie wymagać innego podejścia do zgodności
Jeśli budujesz publiczny serwer MCP obok Rails SaaS, jest to kształt, od którego warto zacząć. Idź stąd.
Powiązane
- Stan sprintu w Twoim agencie; przewodnik praktyka
- Claude Desktop do zarządzania sprintami
- Strona docelowa /mcp: pełna dokumentacja serwera
- /tools/mcp-config-generator: konfiguracja gotowa do wklejenia dla każdego edytora