ai

Onboarding took two days. Now it takes two hours.

Onboarding took two days. Now it takes two hours.

Moving a club onto 1club used to cost us about two working days. Not two days of software — two days of a person. The club would arrive with its members in one export, its plans in a spreadsheet, its payment history in whatever the card processor produced, and its class schedule in a third format again. Someone on our side would reconcile all of it into our shape, load it, find the rows that had not matched, fix them, and load again. It was the slowest part of signing a customer, and the part most likely to put a wrong date on a membership.

That number is now one to two hours, with fewer mistakes, and the reason is not a better importer. It is that we stopped being the only thing allowed to drive the platform.

Two doors

mcp.1club.ai hosts two Model Context Protocol servers. /discovery is anonymous: search clubs, check class, area and instructor availability — everything a member could see on the public marketplace, and nothing an organisation would consider private. /admin is the one this post is about. It requires OAuth 2.1, or a platform API key for developer-managed clients, and every response is limited to the one organisation the connection was granted for.

Behind /admin there are now around seventy-five tools, and every one of them is a thin wrapper over the documented platform API — the same /v1/platform/* endpoints a customer's own integration would call. Nothing reaches the database that the API would not let through. That was the first rule we set and it has held: the MCP server is a new way to call the API, not a new API.

Scopes, not trust

Each tool needs a scope — contacts:read, bookings:write, website:publish, thirty-odd in total — and the scope has to be on the OAuth grant or the key. A connection with website:write but not website:publish can rebuild a club's site all afternoon on a draft that nobody outside can see, and the decision to make it live still belongs to a person. Deleting a contact is contacts:delete, separate from contacts:write, because "correct this email address" and "retire this person, cancelling their memberships and future bookings" should not ride on the same permission.

The wildcard is the detail we argued about longest. A client may request *, and it is expanded at consent time into the full list of scopes that exist that day, and the expansion is what gets stored. A stored * would silently widen every existing grant each time we shipped a new capability, and the consent screen could never show a user a write they had not opted into. So the first thing we tell every connecting assistant is: call get_account, read effectiveScopes, and expect anything outside that list to fail.

The scope list lives in a shared package the rest of the platform imports. The MCP server cannot — it deploys standalone and builds with plain tsc — so it carries a copy, and an architecture test in the main repository reads the copy's source and fails the build if the two lists drift. Four surfaces used to hardcode that list, and they drifted; a scope missing from the metadata is a scope no client can ask for, and the grant quietly freezes.

What it changed

Onboarding. The bulk load now goes through the AI import in the admin portal: it reads the export, proposes the column mapping, previews what would be created, and executes once a person agrees. Then the same person sits in Claude or ChatGPT with the admin server connected and lets the assistant work through the leftovers that never fit a CSV — create_membership, create_booking, update_contact, one odd case at a time. The two days were mostly the leftovers. The hours are mostly review. And because every step is a preview before it is a write, and every write goes through the same validated API as the admin portal, the mistakes we used to find weeks later in a membership with the wrong end date largely stopped appearing.

Reports. We expected this to be an engineering tool; it became a customer one. Club owners connect their own assistant and ask the questions they used to email us: how did Tuesday evenings do this quarter, which plans are churning, what does utilisation look like by court. The assistant pulls the data through the read scopes and renders a report. Early on those came back in whatever colours the model felt like, so we added get_report_theme — a tool that needs no scope, makes no request, and hands over our palette, chart colours, wordmark and a self-contained HTML template. It sits second in the tool list, right after get_account, because anything in the middle of seventy entries gets overlooked. The template links to nothing: the surfaces these documents render on block every external request, so a linked font or logo does not degrade, it vanishes. A test asserts that, mechanically.

Billing that heals. The third change is the one we did not design for. Ask the assistant to list last week's transactions with a failed or pending status and check them against the memberships they should have paid for, and it finds the ones that do not line up: the charge that failed while the membership stayed active, the payment recorded twice, the booking paid for and never attended. Where a tool exists to put it right — amend the membership, cancel the booking — it proposes the fix and, with the scope, makes it; where one does not, it hands the person a list and a link into the portal. That reconciliation used to happen when a customer noticed. Now it happens on a Monday morning because someone asked.

The part that generalises

None of this required the model to be trusted. It required the platform to have an API worth exposing, a permission model fine enough that a client can be given exactly what it needs, and a habit of previewing before writing that we already had for humans. The MCP server added a protocol on top and a description on every tool, and descriptions turned out to be the interface — the model never sees the code, only the paragraph, so the paragraph is where the engineering went.

Two days to two hours is the number we quote. The number we watch is the one that has fallen furthest: the migrations we had to do twice.

Deyan Peev

Written by

Deyan Peev

Founding Engineer · Sofia, Bulgaria

Deyan Peev

Founding Engineer in Sofia, Bulgaria. Currently at 1club.

Elsewhere

© 2026 Deyan Peev