Write whole barbell programmes, not just workouts: weeks, progressions, ramps, and plate maths.
Copy the AI prompt to install this server into Claude Code, Cursor, or another agent β or use 1-click editor setup below.
One-click editor setup isnβt available for this listing yet β we donβt have a confirmed install command, and weβd rather show nothing than point your editor at the wrong package or host. Follow the projectβs own setup instructions, linked above.
Tectonic is a barbell strength-training tracker. An account logs workouts; a workout holds sets of an exercise at a weight and a rep count, in the order they are lifted. All weights are integer pounds.
What makes it more than a list is that a session is written before it is lifted. A program expands into ordinary set rows β warmup ramp included, every weight rounded to something a bar can actually be loaded to β so lifting is tapping through a list that already knows what comes next, and each row keeps the prescription beside what was actually done. An MCP endpoint exposes the same data to an LLM as a connector, which is the second half of this file.
The stack is Roda and Sequel on Postgres, with Rodauth for accounts and rodauth-oauth
for the OAuth 2.1 authorization server the MCP endpoint authenticates against. Views are
ERB with Tailwind from a CDN, and htmx for the parts of the session screen that have to
change without a page load. It deploys to Render.
/workouts/:id/session) β the gym-floor screen, as distinct from
/workouts/:id, which is the record of one. A progress bar across the top, sets grouped
by lift with warmups dimmed, the per-side plate breakdown under each weight ("per side
1Γ25 1Γ10"), and a Done button per set that toggles, so a mis-tap is undone by tapping
again. A set lifted differently is entered inline and the row turns amber rather than
lime, because done-as-written and done-differently have to read differently. The whole
session gets an RPE rating, with a bar-speed guide for choosing one./mcp.You need Ruby at the version in .ruby-version (the Gemfile reads the same file, so the
two cannot drift), a local Postgres, and β only for the browser specs β Firefox with
geckodriver.
dotenv loads .env when the app is required, and .env-example is a file to copy:
cp .env-example .env gives a working development setup, pointed at the
tectonic_development database rake db:create makes below. The keys that are secrets in
a deployment are commented out there rather than left blank, each with the command that
generates it, because a blank is worse than an absence here β dotenv sets the name to the
empty string, every fallback in this project tests for nil, and an empty DATABASE_URL
reaches Sequel.connect and raises rather than falling back to a default.
| Variable | Required | Notes |
|---|---|---|
DATABASE_URL | yes | app.rb connects at require time, so nothing loads without it. .env.rb, which only the Rakefile reads, defaults it from RACK_ENV to postgres:///tectonic_development or postgres:///tectonic_test, but it defaults with ` |
SESSION_SECRET | yes | At least 64 bytes. Roda's sessions plugin refuses a shorter one, and app.rb builds the app at require time, so a short or missing secret raises before a single route is reached. .env-example carries one that is long enough and says in its own text that it is for development; generate a real one with ruby -rsecurerandom -e 'puts SecureRandom.hex(64)' for anything deployed. |
RACK_ENV | no | development unless set, and development is the only value that turns Sequel's query log on; production and staging initialise Sentry and require real OAuth keys. |
DB_LOG | no | Attaches Sequel's query log to stdout whatever RACK_ENV says β set it to anything non-empty to trace queries against a deployment, and unset it again afterwards. Sequel logs statements with their bound values, so those lines carry email addresses and everybody's weights and reps, which is why nothing but development logs by default. |
SENTRY_DSN | no | The Sentry project DSN, read only when RACK_ENV is production or staging. Without it β unset or empty β the app boots and serves with error reporting switched off and says so once on stderr: losing error reporting is not a reason to refuse to start, which is why this behaves unlike OAUTH_JWT_PRIVATE_KEY. |
RACK_TIMEOUT_SERVICE_TIMEOUT | no | Seconds a request may hold a thread before rack-timeout raises inside it, 20 unless set, 0 to switch it off β which is what you want locally the moment you stop in a debugger. Read only by config.ru, so the suite never sees it. It covers both legs of the URLMap, MCP included; the MCP endpoint's long-lived subscriptions/listen streams are unaffected either way, because rack-timeout times app.call and a streaming body is written after that has returned. |
RAILS_MAX_THREADS | no | 5 unless set, and read twice: config/puma.rb gives Puma that many threads and lib/tectonic/db.rb gives Sequel that many connections. One variable because the pool used to be Sequel's default of four against Puma's five threads, and a fifth request thread waiting on a connection is a Sequel::PoolTimeout on a request that had nothing wrong with it. The name is the host's convention rather than a claim about Rails. |
WEB_CONCURRENCY | no | Puma workers, 0 unless set β one process, which is what rackup was already running, and as much as half a CPU has to offer. Above zero Puma forks, so the app is preloaded and each worker disconnects Sequel on boot rather than sharing the parent's connections. Every worker multiplies the connection count by RAILS_MAX_THREADS. It is also what switches worker_timeout on: Puma enforces that in cluster mode alone, so at the default of zero nothing reaps a request still running after the response was handed back, and RACK_TIMEOUT_SERVICE_TIMEOUT is the only timeout in play. |
The MCP and OAuth variables are all optional in development and are documented in the table further down.
db:create shells out to createuser -U postgres and createdb -U postgres -O tectonic,
so it assumes a superuser role named postgres and gives the databases to a tectonic
role. If your Postgres has neither, createdb tectonic_development by hand and go
straight to db:migrate, which only needs DATABASE_URL to point somewhere it can
connect.
The schema is one squashed baseline. db:migrate stamps a database that already carries
the tables rather than trying to rebuild or roll it back, so an existing database from
before the squash adopts the baseline instead of breaking on it.
config.ru mounts two apps side by side under Rack::URLMap: the MCP endpoint at /mcp,
which has its own bearer-token auth and never touches Roda's sessions or CSRF, and the
Roda app at everything else. / redirects to /welcome until you are logged in, so start
by signing up at /create-account.
assets/css/styles.css is compiled from assets/css/app.css and committed, so nothing
but a stylesheet edit needs Node at all β bundle install and a database are enough to run
the app and the suite.
Rebuild it after adding a Tailwind class that was not already in use. CI rebuilds and fails on a difference, so a stale file is caught before it ships rather than after: a purged class breaks nothing anywhere except on the page it was meant to style, in production, silently.
tailwind.config.js scans app.rb and lib/**/*.rb as well as the templates, because
button_style, rpe_style, row_style and the calendar's STYLES table build class names
in Ruby and appear in no template.
It used to be <script src="https://cdn.tailwindcss.com"> β the Play CDN, which compiles in
the browser. That meant no CSS at all with JavaScript off, and a third party on every page
including the consent screen.
Neither variable has to be passed in. spec_helper sets RACK_ENV and then names the
database itself β TEST_DATABASE_URL when a run wants one of its own, and
postgres:///tectonic_test otherwise β whatever the shell, a .env or the Rakefile had
already chosen. That matters more than it looks: spec_helper empties every table after
every test, so a suite pointed at your development database would not seed it, it would
clear it. The built-in exercise library is the one thing kept, because a deployed database
holds it before anybody signs up. rake 'db:reset[name]' still rebuilds a database whose
schema has gone wrong, but a run no longer leaves rows behind for it to clear away.
Two prerequisites, neither obvious from the failure you get without them:
Factual signals from GitHub, npm, and our automated checks β not a rating.
No reviews yet β be the first to share how this listing worked for you.
Showcase your server listing on GitHub or your project documentation. Embed this dynamic SVG badge to highlight official listing status and live engagement.
[](https://allmcps.com/mcp/tectonic-plates)<a href="https://allmcps.com/mcp/tectonic-plates"><img src="https://allmcps.com/api/badge/tectonic-plates?style=directory" alt="Tectonic plates on AllMCPs" /></a>