An agent gets no special path.

Kickgeist exposes seven tools to AI agents over MCP. An agent call runs under exactly the row-level policies a human player runs under, and no path around them exists.

7toolsthe MCP server exposes
one edge worker, two authentication paths to the same toolsRepository, 5 August 2026
48policiesdecide what any caller may do
over 25 tables, beside 51 database functionsRepository, 5 August 2026

The usual way to let an AI agent use your product is to build it a second door. A service account, a scoped key, an integration tier, and a set of rules that resembles the rules humans get without being the same rules. It works. Then one day the two sets disagree, and the agent is holding the wider permission.

Kickgeist has no second door, because it has no first one. There is no API tier at all: the app on a phone talks to Postgres directly, and row-level security is the authorisation boundary. The schema carries 25 tables, 51 database functions and 48 policies, counted from the repository on 5 August 2026. Those policies are the whole answer to what any caller may read or write.

The boundary is the database

Say plainly what this rules out, because it is not the popular choice. There is no Node or Go service in the middle to write, deploy, scale, secure or be paged about at three in the morning. Business logic that has to be trusted lives in database functions with elevated rights and a pinned search path. The client cannot reach around them.

Scoring, referral completion, experience awards and challenge settlement are all database work for that reason. A tampered client can send whatever it likes. It cannot award itself points, because the award does not happen in the client. A leaderboard-tampering exploit found before the tournament was closed at the database layer rather than in the app. The fix therefore reached every build already in players’ hands.

What that made the agent interface cost

Because the boundary was already the database, exposing the product to agents was an interface problem rather than a security problem. The MCP server runs on an edge worker. It exposes seven tools: list matches, make a prediction, create or join a group, read statistics, recover an account. Two authentication paths reach the same tools on the same origin. OAuth 2.1 with a one-tap consent screen, and static API keys for header-only clients.

Then the important part. An agent call mints a token for that individual player and runs under the same policies a human session runs under. The elevated service credential is never used by any client-facing path, the MCP server included. If a policy hides another group’s picks before kickoff, it hides them from the agent for the same reason it hides them from the phone. Neither statement is maintained separately.

The hard parts are the invisible ones

None of that is where the work went. The tools are the easy half. The token handling is where the days went, and the failure modes are not obvious from a design document.

A refresh token that rotates must never be spent twice. Reuse detection treats a double spend as theft and revokes the whole token family. Under concurrent calls a stateless worker did exactly that. The fix was one shared session refresher with an idempotency cache in front. Parallel requests for a session collapse into a single refresh.

Minting the downstream token had the same shape and took the same answer. It is now lazy and serialised inside a durable object, because the stateless version double-spent it under load. A warm object also re-applies its configuration on every request. Without that it happily serves a token minted an hour ago, with no reason to believe it expired.

Persistence fails closed. The server carries DNS-rebinding guards. Agent accounts are marked at signup, in the same atomic step that creates them. A failed follow-up call cannot leave an agent account looking like a person’s.

And the refresh token never reaches the model. That one is a design constraint rather than an implementation detail, and everything above exists to keep it true.

What it costs

Three things, stated as flatly as the benefits.

  • The first is a silent failure mode. Every new table needs an explicit grant beside its policy, because a policy alone returns a 403 with nothing in it. The first time that happened it cost an afternoon. It now lives in the repository’s operating manual, next to where a new table gets created.
  • The second is that authorisation review becomes SQL review. That is a real skill requirement. A team without it will write policies that are subtly wide and pass every test they thought to write.
  • The third is a second public interface. An MCP server is a product surface, so it needs versioning, a specification, a threat model and its own tests. That server carries about 2,100 lines with roughly half again in tests, run in four tiers. The last tier runs against the real worker runtime rather than a stand-in.

When we would not do this

Suppose the product had to talk to three external systems with three trust models, or the write path needed orchestration across services. Then the absent API tier stops being a saving and becomes something you rebuild badly in the client.

This shape suits a product where the data model is the domain model, and the rules can be expressed as row visibility. Kickgeist is that kind of product. The test is not whether you like databases. It is whether your authorisation rules can be written as a predicate over a row, and still be recognisable a year later.

Tell us what is in the way.

Say what you're building and where it stopped.

hello@beevelop.com

Copy it into your mail client if that is easier.

The draft opens with

  1. What you are building
  2. What is in the way
  3. The stack
  4. When you want to start

The contact page says who answers, and what the reply contains.