Project · Epic Games · Dynobase MCP

A curated MCP server for an internal Airtable system

The off-the-shelf MCP would have exposed the entire data layer behind the interface, including everything role-based permissions hide. So I built a curated one. The model sees what the user sees.

In productionRead-onlyThree-tier tool surfaceTypeScript

The team at the studio I'm contracting with, Epic Games, runs their work out of a couple of large Airtable bases. Producers move tasks through the pipeline, video editors track assets, leads watch hours against estimates, and creative technologists keep the schema honest. The system works fine.

What changed is that AI use is being pushed across the company, and people have gotten comfortable enough with Claude to want it inside their actual workflow rather than adjacent to it. Leads want to ask for an actuals-vs-estimates report on hours logged and get one back. Producers want to ask which projects are blocked and why without clicking through.

There's an obvious move here: Airtable already ships an off-the-shelf MCP server. Plug it in, point it at the bases, done. The problem is that the off-the-shelf server exposes the entire data layer behind the interface, including everything role-based permissions hide. That's not what anyone wants. Half the value of the existing interface is that it shapes what each role can see.

So I built a curated one. Read-only by design, scoped to what the user can already see in the interface. The model sees what the user sees.

What got built

A read-only MCP server in TypeScript, with three tiers of tools per major record type:

  • list: filtered enumeration. Fuzzy and partial matches resolve through filters here.
  • analyze: server-side counts and distributions. Response stays small even when the underlying set is large.
  • get: full detail for a single record.

Reference data is simpler. Each tool returns a DTO shaped to mirror the interface the user already knows, not the database schema underneath.

The design philosophy

The hardest decision wasn't technical. It was about the boundary: where does the MCP's surface end?

There are two ways to build an MCP over a system like this. The easy one is to mirror the database: if the schema has eighty columns, the tools expose eighty columns. Every field on every record becomes a field on the DTO, and any user can ask about any of them. That also means the MCP can answer questions the interface was never designed to answer.

I went the other way. The MCP mirrors the interface, not the schema:

  • Each DTO matches what the user can already see at that screen. Detail DTO mirrors the detail page; summary DTO mirrors the list-view columns; identifier DTO mirrors what shows up in a search result.
  • Field names match interface labels, not schema names. If the schema column is scoping_status but the screen calls it "Marketing Status," the DTO key is marketing_status.
  • Analyze tools return raw measurements, not narrative conclusions. They report hours_logged, not overloaded. The caller draws the conclusion.

Every record in every DTO carries a deep-link back to its interface page. The MCP is read-only by design. If a user wants to act on a record, the model surfaces the link and the user clicks through. The boundary stays clean.

What I'd take to the next system

Two ideas pair as a single principle. They're both about making a system robust to a caller that doesn't behave deterministically.

Stable identifiers. When the caller is an AI, identifiers have to be stable. Most platforms have two ways to refer to a thing: an internal ID that doesn't change, and a display label that can be renamed at any time. The display label looks stable. It isn't. AI systems re-resolve references constantly across a conversation, and if the reference resolves to a different thing on the second look than the first, the system loses accuracy in a way that's hard to track down. So everything in this MCP routes through stable IDs, not display strings.

Idempotent operations. Every tool in this MCP is read-only, which means every call is idempotent by construction. The model can retry, re-call, or repeat any tool without side effects. That's not a defensive measure. It's a property the caller actually needs. A model that may revisit any decision across a long conversation has to be safe to revisit it.

Together: identifiers re-resolve to the same thing, operations re-execute to the same outcome. That's a pair I'll take to every AI-driven system that sits over a non-trivial data model.

Where it stands

Live in production. Both the dev and the live environments serve requests. A small tester cohort runs the MCP through real day-to-day work, and I'm iterating based on what they hit.

The early version had a fourth tier of tools: a dedicated search class for fuzzy name lookups. It overlapped with the filtered-list tools enough that the model couldn't reliably tell which one to pick. I retired it. The fix was structural. Fewer tools, sharper distinctions. The lesson is the kind I'd put in a textbook: redundant tools confuse the model. The tool surface is a design artifact, not a feature inventory.