# From working to usable to central

By [Anubhav Choubey](https://anubhavchoubey.com/) · AI & Agents · 2026-11-04 · 4 min read

> An analytics MCP server that was live and correct but unusable, made to hold on the largest store, then moved from one server per store to one central service.

Part 5 of the MCP series. Start at [part 1](/blog/mcp-auth-for-people-and-agents).

```stats
live → usable → central · timeouts that line up · issued credentials · store-by-store cutover
```

## The problem

I had an analytics MCP server that let any MCP client query a store's own data over OAuth, one server per store. It was deployed, protected and correct. And almost nobody could use it.

Getting from "working" to "usable" to "central" took three different kinds of work: fixing how clients connect, making it survive the biggest store, and then changing the architecture underneath without anyone noticing.

## Usable: connecting real clients

The first failures were not code bugs. The production deploy was missing two settings the route needed, so the endpoint was dead config however correct the code was. I found that by reading the live config, not the chart.

The second was the URL itself. An MCP client works out which resource to ask the authorization server for from the URL it was given, then checks it against the server's protected-resource metadata. The SDK's check is a path-prefix test, and my mount point failed it. Nothing about the server was wrong. Moving the mount under the main MCP path made the prefix hold.

Then came guidance. How to write good queries against this data lived in a README and in one assistant's persona. Every other client got a bare tool list and happily wrote the slow, unindexed queries the guidance forbids. So the server now sends short hard rules in its MCP `initialize` instructions, and serves the long reference as an MCP prompt baked into the image.

```callout tip
If guidance only lives in your own client's prompt, every other client runs without it. Put it in the server.
```

## Holding up on the largest store

The largest store found every limit within days.

**Timeouts that never counted as timeouts.** The database had a statement time limit set just below the client's wall, so it always killed a long query first. But that specific error code was never classified. Every real timeout fell through to a generic handler, and the model got a raw driver string instead of advice on how to fix its query. The fix catches the error on both the execute and the fetch side.

**A ladder of limits.** Each layer must give up a little before the one above it, so the error comes from the layer that can explain it:

```text
database hint  <  server wall  <  client tool timeout
```

**The pool.** Under load, the extra query didn't run slowly. It didn't run at all, because the connection pool was smaller than peak concurrency. The database was nowhere near its limit. I made the pool configurable, validated in the deploy workflow so a bad value fails the job instead of crashing the server.

## Central: one service for every store

One server per store had become expensive in workarounds. Analytics was the only MCP that didn't sit behind the gateway like everything else. I wrote a plan that named the two real risks: isolation moves from network topology (store A's server cannot reach store B's database) to logic (the code must pick the right connection every time), and one service down means analytics is down for everyone.

The move happened in three phases.

**Issued credentials.** A provisioning service issues each store's SELECT-only database user on request, and the dashboard stores it encrypted. Write-capable users are never stored, and a test asserts there is no column for them. Every issue is audited.

**The central service.** It runs at least two replicas with a disruption budget, and a single-replica config is refused at render time. The store comes from the live request. Connection pools are keyed by store and database and re-checked when handed out, every failure is a refusal, and write tools are off. I wrote the isolation tests first and ran them against a plausible broken draft to prove they fail. Each store's replica admits the central service only if that store opts in, and its primary database stays blocked. Issued credentials are cached briefly.

**Cutover.** The service joined the gateway only once the gateway's permission layer declared it fail-closed. Then a per-store switch, decided server side on every turn, moved stores across one at a time. Stores without the switch see exactly what they saw before. A store can't be switched while it has state the central path doesn't support yet. CSV export came across later, with a query kill on abort, so nobody lost a feature by moving.

## What I'd do differently

- **Run a real client on day one.** The mount-path bug and the missing guidance were both visible in the first five minutes of a real session.
- **Derive names in one place.** A long store name pushed a generated database user name past the length limit, and the bootstrap still printed "complete". One helper now builds every name.

## Takeaway

"Working" is a low bar for an MCP server. Usable means real clients can connect and get good guidance. Robust means the limits line up and fail with useful errors. Central means isolation becomes code, so test it like it will break, and move stores one at a time.

**Next:** [Plugging in third-party MCP servers safely](/blog/third-party-mcp-servers-safely)
